diff --git a/apps/microtvm/README.md b/apps/microtvm/README.md index 97b844a4c01b..362bc407238e 100644 --- a/apps/microtvm/README.md +++ b/apps/microtvm/README.md @@ -15,14 +15,17 @@ -# microTVM Reference Virtual Machines +# microTVM +microTVM is the effort that allows TVM to build and execute models on bare-metal microcontrollers. -microTVM is the effort to allow TVM to build and execute models on bare-metal microcontrollers. -These Virtual Machines are used to reproduce results and bugs when using microTVM with real -physical hardware. Note that they are not used to run Continuous Integration regression tests-- -those are instead run by the QEMU container (they run against an emulator, rather than real -hardware). +The `pyproject.toml` file in this directory can be used to create a +[Poetry](https://python-poetry.org/) Python environment with all of the required +dependencies installed for running microTVM. To use it, run: + +``` +$ poetry lock && poetry install +$ poetry shell +``` -See the "microTVM Reference Virtual Machines" tutorial for information on how to use these. diff --git a/apps/microtvm/reference-vm/zephyr/pyproject.toml b/apps/microtvm/pyproject.toml similarity index 95% rename from apps/microtvm/reference-vm/zephyr/pyproject.toml rename to apps/microtvm/pyproject.toml index b4cfc544df58..8bfae0a157cd 100644 --- a/apps/microtvm/reference-vm/zephyr/pyproject.toml +++ b/apps/microtvm/pyproject.toml @@ -15,6 +15,9 @@ # specific language governing permissions and limitations # under the License. +# This `pyproject.toml` file is used to allow MicroTVM +# to run within a Poetry-managed environment. + [tool.black] line-length = 100 target-version = ['py36'] @@ -47,12 +50,12 @@ exclude = ''' ) ''' [tool.poetry] -name = "tvm" +name = "microtvm" version = "0.1.0" description = "" -authors = ["Your Name "] +authors = [] packages = [ - { include = "tvm", from = "../../../../python" }, + { include = "tvm", from = "../../python" }, ] [tool.poetry.dependencies] @@ -67,7 +70,6 @@ typed_ast = "^1.4" pyyaml = "^5.4.1" pyserial = "^3.5" - # AutoTVM xgboost = {version = "^1.1", optional = true} diff --git a/apps/microtvm/zephyr/README.md b/apps/microtvm/zephyr/README.md new file mode 100644 index 000000000000..ad00393c0805 --- /dev/null +++ b/apps/microtvm/zephyr/README.md @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + +This directory code to interface microTVM with the [Zephyr RTOS](https://zephyrproject.org/). + diff --git a/tests/micro/qemu/zephyr-runtime/CMakeLists.txt b/apps/microtvm/zephyr/demo_runtime/CMakeLists.txt similarity index 99% rename from tests/micro/qemu/zephyr-runtime/CMakeLists.txt rename to apps/microtvm/zephyr/demo_runtime/CMakeLists.txt index ce5605469fcb..a99d5edb07e6 100644 --- a/tests/micro/qemu/zephyr-runtime/CMakeLists.txt +++ b/apps/microtvm/zephyr/demo_runtime/CMakeLists.txt @@ -9,7 +9,6 @@ set(QEMU_PIPE "\${QEMU_PIPE}") # QEMU_PIPE is set by the calling TVM instance. find_package(Zephyr HINTS $ENV{ZEPHYR_BASE}) project(microtvm_zephyr_runtime) - set(CMAKE_VERBOSE_MAKEFILE ON) file(GLOB TVM_SOURCES ${CMAKE_SOURCE_DIR}/__tvm*.c) target_sources(app PRIVATE src/main.c ${TVM_SOURCES}) diff --git a/apps/microtvm/zephyr/demo_runtime/README.md b/apps/microtvm/zephyr/demo_runtime/README.md new file mode 100644 index 000000000000..eab3f3d241a1 --- /dev/null +++ b/apps/microtvm/zephyr/demo_runtime/README.md @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + +This directory contains a Zephyr-based "demo" runtime environment that +pulls together the microTVM runtime dependencies into a single application +that can communicate with a Python-based host program via the UART, using +TVM's RPC protocol. diff --git a/apps/microtvm/zephyr/demo_runtime/boards/nrf5340dk_nrf5340_cpuapp.conf b/apps/microtvm/zephyr/demo_runtime/boards/nrf5340dk_nrf5340_cpuapp.conf new file mode 100644 index 000000000000..149a69ea3b5b --- /dev/null +++ b/apps/microtvm/zephyr/demo_runtime/boards/nrf5340dk_nrf5340_cpuapp.conf @@ -0,0 +1,31 @@ +# 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. +# +# This file is specific to the nRF5340 DK board. + +# For intrinsics used by generated optimized operators. +CONFIG_CMSIS_DSP=y + +# Required for Cortex-M33 devices. +CONFIG_MAIN_STACK_SIZE=1536 + +# For random number generation. +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y + +# For debugging. +CONFIG_LED=y diff --git a/apps/microtvm/zephyr/demo_runtime/boards/nucleo_f746zg.conf b/apps/microtvm/zephyr/demo_runtime/boards/nucleo_f746zg.conf new file mode 100644 index 000000000000..5931377d55ae --- /dev/null +++ b/apps/microtvm/zephyr/demo_runtime/boards/nucleo_f746zg.conf @@ -0,0 +1,30 @@ +# 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. +# +# This file is specific to the nRF5340 DK board. + +# For intrinsics used by generated optimized operators. +CONFIG_CMSIS_DSP=y + +# Required for Cortex-M33 devices. +CONFIG_MAIN_STACK_SIZE=50 + +# For random number generation. +CONFIG_ENTROPY_GENERATOR=y + +# For debugging. +CONFIG_LED=y diff --git a/tests/micro/qemu/zephyr-runtime/sample.yaml b/apps/microtvm/zephyr/demo_runtime/boards/qemu_x86.conf similarity index 78% rename from tests/micro/qemu/zephyr-runtime/sample.yaml rename to apps/microtvm/zephyr/demo_runtime/boards/qemu_x86.conf index 88616b4acc40..e0e4ae2fb2d3 100644 --- a/tests/micro/qemu/zephyr-runtime/sample.yaml +++ b/apps/microtvm/zephyr/demo_runtime/boards/qemu_x86.conf @@ -15,8 +15,9 @@ # specific language governing permissions and limitations # under the License. -sample: - description: uTVM RPC Server unit test - name: utvm rpc server -common: - tags: introduction +# This file is specific to the QEMU-emulated microTVM board. + +# For TVMPlatformGenerateRandom(). Remember, these values do not need to be truly random. +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_TIMER_RANDOM_GENERATOR=y + diff --git a/tests/micro/qemu/zephyr-runtime/crt/crt_config.h b/apps/microtvm/zephyr/demo_runtime/crt/crt_config.h similarity index 98% rename from tests/micro/qemu/zephyr-runtime/crt/crt_config.h rename to apps/microtvm/zephyr/demo_runtime/crt/crt_config.h index a7f4f90b0538..f8fc7514a28d 100644 --- a/tests/micro/qemu/zephyr-runtime/crt/crt_config.h +++ b/apps/microtvm/zephyr/demo_runtime/crt/crt_config.h @@ -59,6 +59,6 @@ /*! \brief Number of pages on device. */ #define TVM_CRT_MAX_PAGES 300 -//#define TVM_CRT_FRAMER_ENABLE_LOGS +// #define TVM_CRT_FRAMER_ENABLE_LOGS #endif // TVM_RUNTIME_CRT_CONFIG_H_ diff --git a/tests/micro/qemu/zephyr-runtime/prj.conf b/apps/microtvm/zephyr/demo_runtime/prj.conf similarity index 87% rename from tests/micro/qemu/zephyr-runtime/prj.conf rename to apps/microtvm/zephyr/demo_runtime/prj.conf index 7be42b260bbb..bf2b330e35a6 100644 --- a/tests/micro/qemu/zephyr-runtime/prj.conf +++ b/apps/microtvm/zephyr/demo_runtime/prj.conf @@ -15,6 +15,10 @@ # specific language governing permissions and limitations # under the License. +# The settings in this file are generic for all boards, and are merged +# with the settings in the file boards/.conf by the Zephyr build +# process. + # For UART implementation in main(). CONFIG_RING_BUFFER=y CONFIG_UART_CONSOLE=n @@ -30,6 +34,3 @@ CONFIG_FPU=y # For TVMPlatformAbort(). CONFIG_REBOOT=y -# For TVMPlatformGenerateRandom(). Remember, these values do not need to be truly random. -CONFIG_TEST_RANDOM_GENERATOR=y -CONFIG_TIMER_RANDOM_GENERATOR=y diff --git a/tests/micro/qemu/zephyr-runtime/qemu-hack/qemu-system-i386 b/apps/microtvm/zephyr/demo_runtime/qemu-hack/qemu-system-i386 similarity index 100% rename from tests/micro/qemu/zephyr-runtime/qemu-hack/qemu-system-i386 rename to apps/microtvm/zephyr/demo_runtime/qemu-hack/qemu-system-i386 diff --git a/tests/micro/qemu/zephyr-runtime/src/main.c b/apps/microtvm/zephyr/demo_runtime/src/main.c similarity index 56% rename from tests/micro/qemu/zephyr-runtime/src/main.c rename to apps/microtvm/zephyr/demo_runtime/src/main.c index bcd82b41c181..e2aa59af7ad9 100644 --- a/tests/micro/qemu/zephyr-runtime/src/main.c +++ b/apps/microtvm/zephyr/demo_runtime/src/main.c @@ -22,8 +22,16 @@ * SPDX-License-Identifier: Apache-2.0 */ +/* + * This is a sample Zephyr-based application that contains the logic + * needed to control a microTVM-based model via the UART. This is only + * intended to be a demonstration, since typically you will want to incorporate + * this logic into your own application. + */ + #include #include +#include #include #include #include @@ -41,65 +49,107 @@ #include "crt_config.h" -K_SEM_DEFINE(tx_sem, 0, 1); - static const struct device* tvm_uart; -int write_hook(int c) { - uart_poll_out(tvm_uart, c); - return 0; -} +#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 size_t g_num_bytes_requested = 0; +static size_t g_num_bytes_written = 0; + +// Called by TVM to write serial data to the UART. ssize_t write_serial(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; } +// 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) { sys_reboot(SYS_REBOOT_COLD); +#ifdef CONFIG_LED + gpio_pin_set(led0_pin, LED0_PIN, 1); +#endif for (;;) ; } -K_MEM_POOL_DEFINE(tvm_memory_pool, 64, 1024, 120, 4); +// 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; +} + +// Memory pool for use by TVMPlatformMemoryAllocate. +K_MEM_POOL_DEFINE(tvm_memory_pool, 64, 1024, 216, 4); +// Called by TVM to allocate memory. tvm_crt_error_t TVMPlatformMemoryAllocate(size_t num_bytes, DLDevice dev, void** out_ptr) { *out_ptr = k_mem_pool_malloc(&tvm_memory_pool, num_bytes); return (*out_ptr == NULL) ? kTvmErrorPlatformNoMemory : kTvmErrorNoError; } +// Called by TVM to deallocate memory. tvm_crt_error_t TVMPlatformMemoryFree(void* ptr, DLDevice dev) { k_free(ptr); return kTvmErrorNoError; } -uint32_t g_utvm_start_time; - #define MILLIS_TIL_EXPIRY 200 #define TIME_TIL_EXPIRY (K_MSEC(MILLIS_TIL_EXPIRY)) K_TIMER_DEFINE(g_utvm_timer, /* expiry func */ NULL, /* stop func */ NULL); +uint32_t g_utvm_start_time; int g_utvm_timer_running = 0; -#ifdef CONFIG_LED -/* The devicetree node identifier for the "led0" alias. */ -#define LED0_NODE DT_ALIAS(led0) - -#define LED0 DT_GPIO_LABEL(LED0_NODE, gpios) -#define PIN DT_GPIO_PIN(LED0_NODE, gpios) -#define FLAGS DT_GPIO_FLAGS(LED0_NODE, gpios) - -static struct device* led_pin; -#endif // CONFIG_LED - +// Called to start system timer. tvm_crt_error_t TVMPlatformTimerStart() { if (g_utvm_timer_running) { TVMLogf("timer already running"); @@ -107,7 +157,7 @@ tvm_crt_error_t TVMPlatformTimerStart() { } #ifdef CONFIG_LED - gpio_pin_set(led_pin, PIN, 1); + gpio_pin_set(led0_pin, LED0_PIN, 1); #endif k_timer_start(&g_utvm_timer, TIME_TIL_EXPIRY, TIME_TIL_EXPIRY); g_utvm_start_time = k_cycle_get_32(); @@ -115,15 +165,16 @@ tvm_crt_error_t TVMPlatformTimerStart() { return kTvmErrorNoError; } +// Called to stop system timer. tvm_crt_error_t TVMPlatformTimerStop(double* elapsed_time_seconds) { if (!g_utvm_timer_running) { TVMLogf("timer not running"); - return kTvmErrorPlatformTimerBadState; + return kTvmErrorSystemErrorMask | 2; } uint32_t stop_time = k_cycle_get_32(); #ifdef CONFIG_LED - gpio_pin_set(led_pin, PIN, 0); + gpio_pin_set(led0_pin, LED0_PIN, 0); #endif // compute how long the work took @@ -135,7 +186,7 @@ tvm_crt_error_t TVMPlatformTimerStop(double* elapsed_time_seconds) { } uint32_t ns_spent = (uint32_t)k_cyc_to_ns_floor64(cycles_spent); - double hw_clock_elapsed_seconds = ns_spent / 1e9; + double hw_clock_res_us = ns_spent / 1000.0; // need to grab time remaining *before* stopping. when stopped, this function // always returns 0. @@ -144,7 +195,7 @@ tvm_crt_error_t TVMPlatformTimerStop(double* elapsed_time_seconds) { // check *after* stopping to prevent extra expiries on the happy path if (time_remaining_ms < 0) { TVMLogf("negative time remaining"); - return -1; + return kTvmErrorSystemErrorMask | 3; } uint32_t num_expiries = k_timer_status_get(&g_utvm_timer); uint32_t timer_res_ms = ((num_expiries * MILLIS_TIL_EXPIRY) + time_remaining_ms); @@ -153,113 +204,112 @@ tvm_crt_error_t TVMPlatformTimerStop(double* elapsed_time_seconds) { // 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 / 1e3; + *elapsed_time_seconds = timer_res_ms / 1000.0; } else { - *elapsed_time_seconds = hw_clock_elapsed_seconds; + *elapsed_time_seconds = hw_clock_res_us / 1e6; } g_utvm_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; -} - -#define RING_BUF_SIZE 512 -struct uart_rx_buf_t { - struct ring_buf buf; - uint32_t buffer[RING_BUF_SIZE]; -}; +// Ring buffer used to store data read from the UART on rx interrupt. +#define RING_BUF_SIZE_BYTES 4 * 1024 +RING_BUF_DECLARE(uart_rx_rbuf, RING_BUF_SIZE_BYTES); -struct uart_rx_buf_t uart_rx_buf; +// Small buffer used to read data from the UART into the ring buffer. +static uint8_t uart_data[32]; +// 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 uart_rx_buf_t* buf = (struct uart_rx_buf_t*)user_data; - if (uart_irq_rx_ready(dev) == 0) { - continue; - } - - uint8_t data[32]; - for (;;) { - int bytes_read = uart_fifo_read(dev, data, sizeof(data)); - if (bytes_read < 0) { - TVMPlatformAbort(0xbeef); - } else if (bytes_read == 0) { - break; + 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); + } + // CHECK_EQ(bytes_read, bytes_written, "bytes_read: %d; bytes_written: %d", bytes_read, + // bytes_written); } - int bytes_written = ring_buf_put(&buf->buf, data, bytes_read); - CHECK_EQ(bytes_read, bytes_written, "bytes_read: %d; bytes_written: %d", bytes_read, - bytes_written); } } } -void uart_rx_init(struct uart_rx_buf_t* buf, const struct device* dev) { - ring_buf_init(&buf->buf, RING_BUF_SIZE, buf->buffer); - uart_irq_callback_user_data_set(dev, uart_irq_cb, (void*)buf); +// 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 uart_rx_buf_read(struct uart_rx_buf_t* buf, uint8_t* data, size_t data_size_bytes) { +// Used to read data from the UART. +int uart_rx_buf_read(struct ring_buf* rbuf, uint8_t* data, size_t data_size_bytes) { unsigned int key = irq_lock(); - int bytes_read = ring_buf_get(&buf->buf, data, data_size_bytes); + int bytes_read = ring_buf_get(rbuf, data, data_size_bytes); irq_unlock(key); return bytes_read; } +// Buffer used to read from the UART rx ring buffer and feed it to the UTvmRpcServerLoop. +static uint8_t main_rx_buf[RING_BUF_SIZE_BYTES]; + +// The main function of this application. extern void __stdout_hook_install(int (*hook)(int)); void main(void) { #ifdef CONFIG_LED - led_pin = device_get_binding(LED0); - if (led_pin == NULL) { + int ret; + led0_pin = device_get_binding(LED0); + if (led0_pin == NULL) { for (;;) ; } - int ret = gpio_pin_configure(led_pin, PIN, GPIO_OUTPUT_ACTIVE | FLAGS); + ret = gpio_pin_configure(led0_pin, LED0_PIN, GPIO_OUTPUT_ACTIVE | LED0_FLAGS); if (ret < 0) { - for (;;) - ; + TVMPlatformAbort((tvm_crt_error_t)0xbeef4); } - gpio_pin_set(led_pin, PIN, 0); + gpio_pin_set(led0_pin, LED0_PIN, 1); #endif - /* Claim console device */ + // Claim console device. tvm_uart = device_get_binding(DT_LABEL(DT_CHOSEN(zephyr_console))); - uart_rx_init(&uart_rx_buf, tvm_uart); - __stdout_hook_install(&write_hook); + uart_rx_init(&uart_rx_rbuf, tvm_uart); + // Initialize microTVM RPC server, which will receive commands from the UART and execute them. utvm_rpc_server_t server = UTvmRpcServerInit(write_serial, NULL); - TVMLogf("uTVM On-Device Runtime"); + 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 UTvmRpcServerLoop(). while (true) { - uint8_t buf[256]; - int bytes_read = uart_rx_buf_read(&uart_rx_buf, buf, sizeof(buf)); + int bytes_read = uart_rx_buf_read(&uart_rx_rbuf, main_rx_buf, sizeof(main_rx_buf)); if (bytes_read > 0) { size_t bytes_remaining = bytes_read; - uint8_t* cursor = buf; + uint8_t* cursor = main_rx_buf; while (bytes_remaining > 0) { + // Pass the received bytes to the RPC server. tvm_crt_error_t err = UTvmRpcServerLoop(server, &cursor, &bytes_remaining); if (err != kTvmErrorNoError && err != kTvmErrorFramingShortPacket) { TVMPlatformAbort(err); } + if (g_num_bytes_written != 0 || g_num_bytes_requested != 0) { + if (g_num_bytes_written != g_num_bytes_requested) { + TVMPlatformAbort((tvm_crt_error_t)0xbeef5); + } + g_num_bytes_written = 0; + g_num_bytes_requested = 0; + } } } } diff --git a/docs/microtvm/index.rst b/docs/microtvm/index.rst index 2371219af27f..a67b1547d229 100644 --- a/docs/microtvm/index.rst +++ b/docs/microtvm/index.rst @@ -43,7 +43,7 @@ demos run against QEMU and the following hardware: * `STM Nucleo-F746ZG `_ * `STM STM32F746 Discovery `_ -* `nRF 5340 Preview Development Kit `_ +* `nRF 5340 Development Kit `_ Getting Started with microTVM diff --git a/python/tvm/micro/contrib/zephyr.py b/python/tvm/micro/contrib/zephyr.py index cd9c23cd2f9d..104d955835a1 100644 --- a/python/tvm/micro/contrib/zephyr.py +++ b/python/tvm/micro/contrib/zephyr.py @@ -650,10 +650,10 @@ def popen_kwargs(self): env = dict(os.environ) env["ZEPHYR_BASE"] = self._zephyr_base - return dict( + args = dict( args=self._west_cmd + [ - "debug", + "attach", "--skip-rebuild", "--build-dir", self._build_dir, @@ -662,3 +662,4 @@ def popen_kwargs(self): ], env=env, ) + return args diff --git a/python/tvm/runtime/module.py b/python/tvm/runtime/module.py index 41d60683aa3b..5165ae0854fa 100644 --- a/python/tvm/runtime/module.py +++ b/python/tvm/runtime/module.py @@ -454,7 +454,7 @@ def load_module(path, fmt=""): files = [tar_temp.relpath(x) for x in tar_temp.listdir()] _cc.create_shared(path + ".so", files, cc=cc) path += ".so" - # TODO(weberlo): we should probably use a more distinctive suffix for uTVM object files + # TODO(weberlo): we should probably use a more distinctive suffix for microTVM object files elif path.endswith(".obj"): fmt = "micro_dev" # Redirect to the load API diff --git a/python/tvm/target/target.py b/python/tvm/target/target.py index 8c60260e640a..e3ef51158c5a 100644 --- a/python/tvm/target/target.py +++ b/python/tvm/target/target.py @@ -237,26 +237,28 @@ def intel_graphics(model="unknown", options=None): return Target(" ".join(["opencl"] + opts)) +MICRO_SUPPORTED_MODELS = { + "host": [], + "stm32f746xx": ["-mcpu=cortex-m7", "-march=armv7e-m"], + "nrf5340dk": ["-mcpu=cortex-m33"], +} + + def micro(model="unknown", options=None): """Returns a microTVM target. Parameters ---------- model : str - Canonically identifies the target device. This is typically a CPU or board level name (other - flags such as -mcpu identify the ISA). + Canonically identifies the target device. This is typically a device board level name. + The allowed values are MICRO_SUPPORTED_MODELS.keys(). options : str or list of str Additional options """ - trans_table = { - "host": [], - "stm32f746xx": ["-mcpu=cortex-m7", "-march=armv7e-m"], - "nrf5340dk": ["-mcpu=cortex-m33"], - } - if model not in trans_table: + if model not in MICRO_SUPPORTED_MODELS: raise ValueError(f"Model {model} not supported by tvm.target.micro.") opts = _merge_opts( - trans_table[model] + ["-runtime=c", "--system-lib", f"-model={model}"], + MICRO_SUPPORTED_MODELS[model] + ["-runtime=c", "--system-lib", f"-model={model}"], options, ) diff --git a/tests/lint/check_file_type.py b/tests/lint/check_file_type.py index ab51b6c79c83..8d8b34322de3 100644 --- a/tests/lint/check_file_type.py +++ b/tests/lint/check_file_type.py @@ -125,9 +125,16 @@ "docs/_static/img/tvm-logo-square.png", # pytest config "pytest.ini", - # Zephyr tests - "tests/micro/qemu/zephyr-runtime/prj.conf", - "tests/micro/qemu/zephyr-runtime/qemu-hack/qemu-system-i386", + # microTVM tests + "tests/micro/zephyr/testdata/digit-2.jpg", + "tests/micro/zephyr/testdata/digit-9.jpg", + "tests/micro/zephyr/testdata/mnist-8.onnx", + # microTVM Zephyr runtime + "apps/microtvm/zephyr/demo_runtime/prj.conf", + "apps/microtvm/zephyr/demo_runtime/boards/nrf5340dk_nrf5340_cpuapp.conf", + "apps/microtvm/zephyr/demo_runtime/boards/nucleo_f746zg.conf", + "apps/microtvm/zephyr/demo_runtime/boards/qemu_x86.conf", + "apps/microtvm/zephyr/demo_runtime/qemu-hack/qemu-system-i386", # microTVM Virtual Machines "apps/microtvm/reference-vm/zephyr/Vagrantfile", "apps/microtvm/reference-vm/zephyr/base-box/Vagrantfile.packer-template", diff --git a/tests/micro/qemu/.gitignore b/tests/micro/qemu/.gitignore deleted file mode 100644 index c920d8f93ff8..000000000000 --- a/tests/micro/qemu/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/test_zephyr*-workspace -/*.micro-binary diff --git a/tests/micro/qemu/zephyr-runtime/.gitignore b/tests/micro/qemu/zephyr-runtime/.gitignore deleted file mode 100644 index 64be5d3a487c..000000000000 --- a/tests/micro/qemu/zephyr-runtime/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -__tvm* -libtvm__* -/build diff --git a/tests/micro/zephyr/README.md b/tests/micro/zephyr/README.md new file mode 100644 index 000000000000..9769cae2b53b --- /dev/null +++ b/tests/micro/zephyr/README.md @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + +This directory contains tests for MicroTVM's integration with Zephyr. + +To run the test, you first need to be running in a Python environment with +all of the appropriate TVM dependencies installed. If you have [Poetry](https://python-poetry.org/) +installed, you can do the following to get an appropriately-configured Python +environment: + +``` +$ cd tvm/apps/microtvm/ +$ poetry lock && poetry install && poetry shell +``` + +You can then run this test (either on real hardware or on a QEMU-emulated +device) using: + +``` +$ cd tvm/tests/micro/zephyr +$ pytest test_zephyr.py --microtvm-platforms=host # For QEMU emulation +$ pytest test_zephyr.py --microtvm-platforms=nrf5340dk # For nRF5340DK +``` + +To see the list of supported values for `--microtvm-platforms`, run: +``` +$ pytest test_zephyr.py --help +``` diff --git a/tests/micro/qemu/conftest.py b/tests/micro/zephyr/conftest.py similarity index 94% rename from tests/micro/qemu/conftest.py rename to tests/micro/zephyr/conftest.py index 3fc54df02063..e8ce443adfaf 100644 --- a/tests/micro/qemu/conftest.py +++ b/tests/micro/zephyr/conftest.py @@ -16,11 +16,14 @@ # under the License. import pytest +import tvm.target.target + def pytest_addoption(parser): parser.addoption( "--microtvm-platforms", default="host", + choices=tvm.target.target.MICRO_SUPPORTED_MODELS.keys(), help=( "Specify a comma-separated list of test models (i.e. as passed to tvm.target.micro()) " "for microTVM tests." diff --git a/tests/micro/qemu/test_zephyr.py b/tests/micro/zephyr/test_zephyr.py similarity index 81% rename from tests/micro/qemu/test_zephyr.py rename to tests/micro/zephyr/test_zephyr.py index 1e7eed7b906a..003cd54bba90 100644 --- a/tests/micro/qemu/test_zephyr.py +++ b/tests/micro/zephyr/test_zephyr.py @@ -19,12 +19,15 @@ import copy import datetime import glob +import logging import os import subprocess import sys import pytest import numpy as np +import onnx +from PIL import Image import tvm import tvm.rpc @@ -36,11 +39,14 @@ from tvm.relay.expr_functor import ExprMutator from tvm.relay.op.annotation import compiler_begin, compiler_end +# If set, build the uTVM binary from scratch on each test. +# Otherwise, reuses the build from the previous test run. BUILD = True -DEBUG = False - -TARGET = None +# If set, enable a debug session while the test is running. +# Before running the test, in a separate shell, you should run: +# python -m tvm.exec.microtvm_debug_shell +DEBUG = False def _make_sess_from_op(model, zephyr_board, west_cmd, op_name, sched, arg_bufs): @@ -62,15 +68,17 @@ def _make_session(model, target, zephyr_board, west_cmd, mod): os.makedirs(workspace_parent) workspace = tvm.micro.Workspace(debug=True, root=workspace_root) - project_dir = os.path.join(os.path.dirname(__file__) or ".", "zephyr-runtime") + test_dir = os.path.dirname(os.path.realpath(os.path.expanduser(__file__))) + tvm_source_dir = os.path.join(test_dir, "..", "..", "..") + runtime_path = os.path.join(tvm_source_dir, "apps", "microtvm", "zephyr", "demo_runtime") compiler = zephyr.ZephyrCompiler( - project_dir=project_dir, + project_dir=runtime_path, board=zephyr_board, zephyr_toolchain_variant="zephyr", west_cmd=west_cmd, ) - opts = tvm.micro.default_options(f"{project_dir}/crt") + opts = tvm.micro.default_options(os.path.join(runtime_path, "crt")) # TODO(weberlo) verify this is necessary opts["bin_opts"]["ccflags"] = ["-std=gnu++14"] opts["lib_opts"]["ccflags"] = ["-std=gnu++14"] @@ -201,6 +209,53 @@ def test_relay(platform, west_cmd): tvm.testing.assert_allclose(result, x_in * x_in + 1) +def test_onnx(platform, west_cmd): + """Testing a simple ONNX model.""" + model, zephyr_board = PLATFORMS[platform] + + # Load test images. + this_dir = os.path.dirname(__file__) + digit_2 = Image.open(f"{this_dir}/testdata/digit-2.jpg").resize((28, 28)) + digit_2 = np.asarray(digit_2).astype("float32") + digit_2 = np.expand_dims(digit_2, axis=0) + + digit_9 = Image.open(f"{this_dir}/testdata/digit-9.jpg").resize((28, 28)) + digit_9 = np.asarray(digit_9).astype("float32") + digit_9 = np.expand_dims(digit_9, axis=0) + + # Load ONNX model and convert to Relay. + onnx_model = onnx.load(f"{this_dir}/testdata/mnist-8.onnx") + shape = {"Input3": (1, 1, 28, 28)} + relay_mod, params = relay.frontend.from_onnx(onnx_model, shape=shape, freeze_params=True) + relay_mod = relay.transform.DynamicToStatic()(relay_mod) + + # We add the -link-params=1 option to ensure the model parameters are compiled in. + # There is currently a bug preventing the demo_runtime environment from receiving + # the model weights when set using graph_mod.set_input(). + # See: https://github.com/apache/tvm/issues/7567 + target = tvm.target.target.micro(model, options=["-link-params=1"]) + with tvm.transform.PassContext(opt_level=3, config={"tir.disable_vectorize": True}): + lowered = relay.build(relay_mod, target, params=params) + graph = lowered.get_json() + + with _make_session(model, target, zephyr_board, west_cmd, lowered.lib) as session: + graph_mod = tvm.micro.create_local_graph_runtime( + graph, session.get_system_lib(), session.device + ) + + # Send the digit-2 image and confirm that the correct result is returned. + graph_mod.set_input("Input3", tvm.nd.array(digit_2)) + graph_mod.run() + result = graph_mod.get_output(0).asnumpy() + assert np.argmax(result) == 2 + + # Send the digit-9 image and confirm that the correct result is returned. + graph_mod.set_input("Input3", tvm.nd.array(digit_9)) + graph_mod.run() + result = graph_mod.get_output(0).asnumpy() + assert np.argmax(result) == 9 + + class CcompilerAnnotator(ExprMutator): """ This is used to create external functions for ccompiler. diff --git a/tests/micro/zephyr/testdata/digit-2.jpg b/tests/micro/zephyr/testdata/digit-2.jpg new file mode 100644 index 000000000000..b709a206b8d7 Binary files /dev/null and b/tests/micro/zephyr/testdata/digit-2.jpg differ diff --git a/tests/micro/zephyr/testdata/digit-9.jpg b/tests/micro/zephyr/testdata/digit-9.jpg new file mode 100644 index 000000000000..6ce9cde3b322 Binary files /dev/null and b/tests/micro/zephyr/testdata/digit-9.jpg differ diff --git a/tests/micro/zephyr/testdata/mnist-8.onnx b/tests/micro/zephyr/testdata/mnist-8.onnx new file mode 100644 index 000000000000..fc1a3f733c6e Binary files /dev/null and b/tests/micro/zephyr/testdata/mnist-8.onnx differ diff --git a/tests/scripts/task_python_microtvm.sh b/tests/scripts/task_python_microtvm.sh index 2e06932ba536..1c202c0ea40c 100755 --- a/tests/scripts/task_python_microtvm.sh +++ b/tests/scripts/task_python_microtvm.sh @@ -26,4 +26,4 @@ source tests/scripts/setup-pytest-env.sh find . -type f -path "*.pyc" | xargs rm -f make cython3 -run_pytest ctypes python-microtvm-qemu tests/micro/qemu +run_pytest ctypes python-microtvm-zephyr tests/micro/zephyr diff --git a/tutorials/micro/micro_tflite.py b/tutorials/micro/micro_tflite.py index d86fc5838c43..53271b29e20d 100644 --- a/tutorials/micro/micro_tflite.py +++ b/tutorials/micro/micro_tflite.py @@ -177,7 +177,7 @@ # Now we create a build config for relay. turning off two options # and then calling relay.build which will result in a C source # file. When running on a simulated target, choose "host" below: -TARGET = tvm.target.target.micro("host") +# TARGET = tvm.target.target.micro("host") # %% # Compiling for physical hardware @@ -190,8 +190,9 @@ # # .. code-block:: python # -# TARGET = tvm.target.target.micro("stm32f746xx") -# BOARD = "nucleo_f746zg" # or "stm32f746g_disco" +TARGET = tvm.target.target.micro("host") +# BOARD = "nucleo_f746zg" # or "stm32f746g_disco" +BOARD = "qemu_x86" ###################################################################### # Now, compile the model for the target: