You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tvm.apache.org by GitBox <gi...@apache.org> on 2021/08/10 23:10:55 UTC
[GitHub] [tvm] areusch commented on a change in pull request #8708: [microTVM] Project API Arduino support
areusch commented on a change in pull request #8708:
URL: https://github.com/apache/tvm/pull/8708#discussion_r686368224
##########
File path: apps/microtvm/arduino/example_project/src/model.c
##########
@@ -0,0 +1,82 @@
+#ifndef TVM_IMPLEMENTATION_ARDUINO
+#define TVM_IMPLEMENTATION_ARDUINO
+
+#include "model.h"
+
+#include "Arduino.h"
+#include "standalone_crt/include/tvm/runtime/crt/internal/aot_executor/aot_executor.h"
+#include "standalone_crt/include/tvm/runtime/crt/stack_allocator.h"
+
+// AOT memory array
+static uint8_t g_aot_memory[WORKSPACE_SIZE];
+extern tvm_model_t tvmgen_default_network;
+tvm_workspace_t app_workspace;
+
+// Blink code for debugging purposes
+void TVMPlatformAbort(tvm_crt_error_t error) {
+ for (;;) {
+#ifdef LED_BUILTIN
Review comment:
should we #error if not? should we also consider TVMLogf() or Serial.write something?
##########
File path: apps/microtvm/arduino/example_project/project.ino
##########
@@ -0,0 +1,10 @@
+#include "src/model.h"
+
+void setup() {
+ TVMInitialize();
+ //TVMExecute(input_data, output_data);
Review comment:
uncomment?
##########
File path: apps/microtvm/arduino/example_project/src/model.h
##########
@@ -0,0 +1,19 @@
+#ifndef IMPLEMENTATION_H_
+#define IMPLEMENTATION_H_
+
+#define WORKSPACE_SIZE $workspace_size_bytes
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void TVMInitialize();
+
+// TODO template these void* values once MLF format has input and output data
Review comment:
could you be more specific? "once MLF has JSON metadata describing the model inputs and outputs"
##########
File path: apps/microtvm/arduino/example_project/src/model.c
##########
@@ -0,0 +1,82 @@
+#ifndef TVM_IMPLEMENTATION_ARDUINO
+#define TVM_IMPLEMENTATION_ARDUINO
+
+#include "model.h"
+
+#include "Arduino.h"
+#include "standalone_crt/include/tvm/runtime/crt/internal/aot_executor/aot_executor.h"
+#include "standalone_crt/include/tvm/runtime/crt/stack_allocator.h"
+
+// AOT memory array
+static uint8_t g_aot_memory[WORKSPACE_SIZE];
+extern tvm_model_t tvmgen_default_network;
+tvm_workspace_t app_workspace;
+
+// Blink code for debugging purposes
+void TVMPlatformAbort(tvm_crt_error_t error) {
+ for (;;) {
+#ifdef LED_BUILTIN
+ digitalWrite(LED_BUILTIN, HIGH);
+ delay(250);
+ digitalWrite(LED_BUILTIN, LOW);
+ delay(250);
+ digitalWrite(LED_BUILTIN, HIGH);
+ delay(250);
+ digitalWrite(LED_BUILTIN, LOW);
+ delay(750);
+#endif
+ }
+}
+
+void TVMLogf(const char* msg, ...) {}
+
+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);
+}
+
+unsigned long g_utvm_start_time;
Review comment:
suggest a units suffix: `g_utvm_start_time_micros`
##########
File path: apps/microtvm/arduino/host_driven/project.ino
##########
@@ -0,0 +1,34 @@
+#include "src/standalone_crt/include/tvm/runtime/crt/microtvm_rpc_server.h"
+#include "src/standalone_crt/include/tvm/runtime/crt/logging.h"
+#include "src/model.h"
+microtvm_rpc_server_t server;
+
+// Called by TVM to write serial data to the UART.
+ssize_t write_serial(void* unused_context, const uint8_t* data, size_t size) {
+ Serial.write(data, size);
+ return size;
+}
+
+void setup() {
+ server = MicroTVMRpcServerInit(write_serial, NULL);
+ TVMLogf("microTVM Arduino runtime - running");
+ Serial.begin(115200);
+}
+
+void loop() {
+ int to_read = Serial.available();
+ uint8_t data[to_read];
+ size_t bytes_read = Serial.readBytes(data, to_read);
+ uint8_t* arr_ptr = data;
+ uint8_t** data_ptr = &arr_ptr;
+ if (bytes_read > 0) {
Review comment:
suggest simplifying to just the inner while loop. you could even change `bytes_read` to `bytes_remaining` above
##########
File path: apps/microtvm/arduino/host_driven/src/model.c
##########
@@ -0,0 +1,73 @@
+#ifndef TVM_IMPLEMENTATION_ARDUINO
+#define TVM_IMPLEMENTATION_ARDUINO
+
+#include "model.h"
+
+#include "Arduino.h"
+#include "standalone_crt/include/tvm/runtime/crt/internal/aot_executor/aot_executor.h"
+#include "stdarg.h"
+
+// Blink code for debugging purposes
+void TVMPlatformAbort(tvm_crt_error_t error) {
+ for (;;) {
+ for (int i = 0; i < 4; i++) {
+ digitalWrite(LED_BUILTIN, HIGH);
+ delay(250);
+ digitalWrite(LED_BUILTIN, LOW);
+ delay(250);
+ }
+ delay(1000);
+ }
+}
+
+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);
+}
+
+tvm_crt_error_t TVMPlatformMemoryAllocate(size_t num_bytes, DLDevice dev, void** out_ptr) {
+ if (num_bytes == 0) {
+ num_bytes = sizeof(int);
+ }
+ *out_ptr = malloc(num_bytes);
+ return (*out_ptr == NULL) ? kTvmErrorPlatformNoMemory : kTvmErrorNoError;
+}
+
+tvm_crt_error_t TVMPlatformMemoryFree(void* ptr, DLDevice dev) {
+ free(ptr);
+ return kTvmErrorNoError;
+}
+
+unsigned long g_utvm_start_time;
Review comment:
micros suffix
##########
File path: apps/microtvm/arduino/template_project/microtvm_api_server.py
##########
@@ -0,0 +1,423 @@
+import collections
+import functools
+import json
+import logging
+import os
+import os.path
+import pathlib
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tarfile
+from string import Template
+import tempfile
+import time
+
+import serial
+import serial.tools.list_ports
+
+from tvm.micro.project_api import server
+
+MODEL_LIBRARY_FORMAT_RELPATH = pathlib.Path("src") / "model" / "model.tar"
+API_SERVER_DIR = pathlib.Path(os.path.dirname(__file__) or os.path.getcwd())
+BUILD_DIR = API_SERVER_DIR / "build"
+MODEL_LIBRARY_FORMAT_PATH = API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH
+
+IS_TEMPLATE = not (API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH).exists()
+
+
+class InvalidPortException(Exception):
+ """Raised when the given port could not be opened"""
+
+
+class SketchUploadException(Exception):
+ """Raised when a sketch cannot be uploaded for an unknown reason."""
+
+
+class BoardAutodetectFailed(Exception):
+ """Raised when no attached hardware is found matching the requested board"""
+
+
+BOARD_PROPERTIES = {
Review comment:
comment what this data structure is -- what are the keys and values?
##########
File path: apps/microtvm/arduino/host_driven/src/model.c
##########
@@ -0,0 +1,73 @@
+#ifndef TVM_IMPLEMENTATION_ARDUINO
+#define TVM_IMPLEMENTATION_ARDUINO
+
+#include "model.h"
+
+#include "Arduino.h"
+#include "standalone_crt/include/tvm/runtime/crt/internal/aot_executor/aot_executor.h"
+#include "stdarg.h"
+
+// Blink code for debugging purposes
+void TVMPlatformAbort(tvm_crt_error_t error) {
+ for (;;) {
Review comment:
same question--should we TVMLogf() something?
##########
File path: apps/microtvm/arduino/template_project/microtvm_api_server.py
##########
@@ -0,0 +1,423 @@
+import collections
+import functools
+import json
+import logging
+import os
+import os.path
+import pathlib
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tarfile
+from string import Template
+import tempfile
+import time
+
+import serial
+import serial.tools.list_ports
+
+from tvm.micro.project_api import server
+
+MODEL_LIBRARY_FORMAT_RELPATH = pathlib.Path("src") / "model" / "model.tar"
+API_SERVER_DIR = pathlib.Path(os.path.dirname(__file__) or os.path.getcwd())
+BUILD_DIR = API_SERVER_DIR / "build"
+MODEL_LIBRARY_FORMAT_PATH = API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH
+
+IS_TEMPLATE = not (API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH).exists()
+
+
+class InvalidPortException(Exception):
+ """Raised when the given port could not be opened"""
+
+
+class SketchUploadException(Exception):
+ """Raised when a sketch cannot be uploaded for an unknown reason."""
+
+
+class BoardAutodetectFailed(Exception):
+ """Raised when no attached hardware is found matching the requested board"""
+
+
+BOARD_PROPERTIES = {
+ "due": {"package": "arduino", "architecture": "sam", "board": "arduino_due_x"},
+ # Due to the way the Feather S2 bootloader works, compilation
+ # behaves fine but uploads cannot be done automatically
+ "feathers2": {
+ "package": "esp32",
+ "architecture": "esp32",
+ "board": "feathers2",
+ },
+ # Spresense only works as of its v2.3.0 sdk
+ "spresense": {
+ "package": "SPRESENSE",
+ "architecture": "spresense",
+ "board": "spresense",
+ },
+ "nano33ble": {
+ "package": "arduino",
+ "architecture": "mbed_nano",
+ "board": "nano33ble",
+ },
+ "pybadge": {
+ "package": "adafruit",
+ "architecture": "samd",
+ "board": "adafruit_pybadge_m4",
+ },
+ # The Teensy boards are listed here for completeness, but they
+ # won't work until https://github.com/arduino/arduino-cli/issues/700
+ # is finished
+ "teensy40": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy40",
+ },
+ "teensy41": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy41",
+ },
+}
+
+PROJECT_TYPES = ["example_project", "host_driven"]
+
+PROJECT_OPTIONS = [
+ server.ProjectOption(
+ "arduino_board",
+ choices=list(BOARD_PROPERTIES),
+ help="Name of the Arduino board to build for",
+ ),
+ server.ProjectOption("arduino_cli_cmd", help="Path to the arduino-cli tool."),
+ server.ProjectOption("port", help="Port to use for connecting to hardware"),
+ server.ProjectOption(
+ "example_project",
Review comment:
should this be project_type?
##########
File path: apps/microtvm/arduino/template_project/microtvm_api_server.py
##########
@@ -0,0 +1,423 @@
+import collections
+import functools
+import json
+import logging
+import os
+import os.path
+import pathlib
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tarfile
+from string import Template
+import tempfile
+import time
+
+import serial
+import serial.tools.list_ports
+
+from tvm.micro.project_api import server
+
+MODEL_LIBRARY_FORMAT_RELPATH = pathlib.Path("src") / "model" / "model.tar"
+API_SERVER_DIR = pathlib.Path(os.path.dirname(__file__) or os.path.getcwd())
+BUILD_DIR = API_SERVER_DIR / "build"
+MODEL_LIBRARY_FORMAT_PATH = API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH
+
+IS_TEMPLATE = not (API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH).exists()
+
+
+class InvalidPortException(Exception):
+ """Raised when the given port could not be opened"""
+
+
+class SketchUploadException(Exception):
+ """Raised when a sketch cannot be uploaded for an unknown reason."""
+
+
+class BoardAutodetectFailed(Exception):
+ """Raised when no attached hardware is found matching the requested board"""
+
+
+BOARD_PROPERTIES = {
+ "due": {"package": "arduino", "architecture": "sam", "board": "arduino_due_x"},
+ # Due to the way the Feather S2 bootloader works, compilation
+ # behaves fine but uploads cannot be done automatically
+ "feathers2": {
+ "package": "esp32",
+ "architecture": "esp32",
+ "board": "feathers2",
+ },
+ # Spresense only works as of its v2.3.0 sdk
+ "spresense": {
+ "package": "SPRESENSE",
+ "architecture": "spresense",
+ "board": "spresense",
+ },
+ "nano33ble": {
+ "package": "arduino",
+ "architecture": "mbed_nano",
+ "board": "nano33ble",
+ },
+ "pybadge": {
+ "package": "adafruit",
+ "architecture": "samd",
+ "board": "adafruit_pybadge_m4",
+ },
+ # The Teensy boards are listed here for completeness, but they
+ # won't work until https://github.com/arduino/arduino-cli/issues/700
+ # is finished
+ "teensy40": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy40",
+ },
+ "teensy41": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy41",
+ },
+}
+
+PROJECT_TYPES = ["example_project", "host_driven"]
+
+PROJECT_OPTIONS = [
+ server.ProjectOption(
+ "arduino_board",
+ choices=list(BOARD_PROPERTIES),
+ help="Name of the Arduino board to build for",
+ ),
+ server.ProjectOption("arduino_cli_cmd", help="Path to the arduino-cli tool."),
+ server.ProjectOption("port", help="Port to use for connecting to hardware"),
+ server.ProjectOption(
+ "example_project",
+ help="Type of project to generate.",
+ choices=tuple(PROJECT_TYPES),
+ ),
+ server.ProjectOption(
+ "verbose", help="True to pass --verbose flag to arduino-cli compile and upload"
+ ),
+]
+
+
+class Handler(server.ProjectAPIHandler):
+ def __init__(self):
+ super(Handler, self).__init__()
+ self._proc = None
+ self._port = None
+ self._serial = None
+
+ def server_info_query(self, tvm_version):
+ return server.ServerInfo(
+ platform_name="arduino",
+ is_template=IS_TEMPLATE,
+ model_library_format_path=MODEL_LIBRARY_FORMAT_PATH,
+ project_options=PROJECT_OPTIONS,
+ )
+
+ def _copy_project_files(self, api_server_dir, project_dir, project_type):
+ project_types_folder = api_server_dir.parents[0]
+ shutil.copytree(
+ project_types_folder / project_type / "src", project_dir / "src", dirs_exist_ok=True
+ )
+ # Arduino requires the .ino file have the same filename as its containing folder
+ shutil.copy2(
+ project_types_folder / project_type / "project.ino",
+ project_dir / f"{project_dir.stem}.ino",
+ )
+
+ CRT_COPY_ITEMS = ("include", "src")
+
+ def _copy_standalone_crt(self, source_dir, standalone_crt_dir):
+ # Copy over the standalone_crt directory
+ output_crt_dir = source_dir / "standalone_crt"
+ for item in self.CRT_COPY_ITEMS:
+ src_path = os.path.join(standalone_crt_dir, item)
+ dst_path = output_crt_dir / item
+ if os.path.isdir(src_path):
+ shutil.copytree(src_path, dst_path)
+ else:
+ shutil.copy2(src_path, dst_path)
+
+ # Example project is the "minimum viable project",
+ # and doesn't need a fancy RPC server
+ EXAMPLE_PROJECT_UNUSED_COMPONENTS = [
+ "include/dmlc",
+ "src/support",
+ "src/runtime/minrpc",
+ "src/runtime/crt/graph_executor",
+ "src/runtime/crt/microtvm_rpc_common",
+ "src/runtime/crt/microtvm_rpc_server",
+ "src/runtime/crt/tab",
+ ]
+
+ def _remove_unused_components(self, source_dir, project_type):
+ unused_components = []
+ if project_type == "example_project":
+ unused_components = self.EXAMPLE_PROJECT_UNUSED_COMPONENTS
+
+ for component in unused_components:
+ shutil.rmtree(source_dir / "standalone_crt" / component)
+
+ def _disassemble_mlf(self, mlf_tar_path, source_dir):
+ with tempfile.TemporaryDirectory() as mlf_unpacking_dir_str:
+ mlf_unpacking_dir = pathlib.Path(mlf_unpacking_dir_str)
+ with tarfile.open(mlf_tar_path, "r:") as tar:
+ tar.extractall(mlf_unpacking_dir)
+
+ model_dir = source_dir / "model"
+ model_dir.mkdir()
+
+ # Copy C files from model. The filesnames and quantity
+ # depend on the target string, so we just copy all c files
+ source_dir = mlf_unpacking_dir / "codegen" / "host" / "src"
+ for file in source_dir.rglob(f"*.c"):
+ shutil.copy(file, model_dir)
+
+ # Return metadata.json for use in templating
+ with open(os.path.join(mlf_unpacking_dir, "metadata.json")) as f:
+ metadata = json.load(f)
+ return metadata
+
+ def _template_model_header(self, source_dir, metadata):
+ with open(source_dir / "model.h", "r") as f:
+ model_h_template = Template(f.read())
+
+ template_values = {
+ "workspace_size_bytes": metadata["memory"]["functions"]["main"][0][
+ "workspace_size_bytes"
+ ],
+ }
+
+ with open(source_dir / "model.h", "w") as f:
+ f.write(model_h_template.substitute(template_values))
+
+ # Arduino ONLY recognizes .ino, .ccp, .c, .h
+
+ CPP_FILE_EXTENSION_SYNONYMS = ("cc", "cxx")
+
+ def _change_cpp_file_extensions(self, source_dir):
+ for ext in self.CPP_FILE_EXTENSION_SYNONYMS:
+ for filename in source_dir.rglob(f"*.{ext}"):
+ filename.rename(filename.with_suffix(".cpp"))
+
+ for filename in source_dir.rglob(f"*.inc"):
+ filename.rename(filename.with_suffix(".h"))
+
+ def _process_autogenerated_inc_files(self, source_dir):
+ for filename in source_dir.rglob(f"*.inc"):
+ # Individual file fixes
+ if filename.stem == "gentab_ccitt":
Review comment:
is it worth just comparing the whole filename here to make it clearer?
##########
File path: apps/microtvm/arduino/template_project/microtvm_api_server.py
##########
@@ -0,0 +1,423 @@
+import collections
+import functools
+import json
+import logging
+import os
+import os.path
+import pathlib
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tarfile
+from string import Template
+import tempfile
+import time
+
+import serial
+import serial.tools.list_ports
+
+from tvm.micro.project_api import server
+
+MODEL_LIBRARY_FORMAT_RELPATH = pathlib.Path("src") / "model" / "model.tar"
+API_SERVER_DIR = pathlib.Path(os.path.dirname(__file__) or os.path.getcwd())
+BUILD_DIR = API_SERVER_DIR / "build"
+MODEL_LIBRARY_FORMAT_PATH = API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH
+
+IS_TEMPLATE = not (API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH).exists()
+
+
+class InvalidPortException(Exception):
+ """Raised when the given port could not be opened"""
+
+
+class SketchUploadException(Exception):
+ """Raised when a sketch cannot be uploaded for an unknown reason."""
+
+
+class BoardAutodetectFailed(Exception):
+ """Raised when no attached hardware is found matching the requested board"""
+
+
+BOARD_PROPERTIES = {
+ "due": {"package": "arduino", "architecture": "sam", "board": "arduino_due_x"},
+ # Due to the way the Feather S2 bootloader works, compilation
+ # behaves fine but uploads cannot be done automatically
+ "feathers2": {
+ "package": "esp32",
+ "architecture": "esp32",
+ "board": "feathers2",
+ },
+ # Spresense only works as of its v2.3.0 sdk
+ "spresense": {
+ "package": "SPRESENSE",
+ "architecture": "spresense",
+ "board": "spresense",
+ },
+ "nano33ble": {
+ "package": "arduino",
+ "architecture": "mbed_nano",
+ "board": "nano33ble",
+ },
+ "pybadge": {
+ "package": "adafruit",
+ "architecture": "samd",
+ "board": "adafruit_pybadge_m4",
+ },
+ # The Teensy boards are listed here for completeness, but they
+ # won't work until https://github.com/arduino/arduino-cli/issues/700
+ # is finished
+ "teensy40": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy40",
+ },
+ "teensy41": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy41",
+ },
+}
+
+PROJECT_TYPES = ["example_project", "host_driven"]
+
+PROJECT_OPTIONS = [
+ server.ProjectOption(
+ "arduino_board",
+ choices=list(BOARD_PROPERTIES),
+ help="Name of the Arduino board to build for",
+ ),
+ server.ProjectOption("arduino_cli_cmd", help="Path to the arduino-cli tool."),
+ server.ProjectOption("port", help="Port to use for connecting to hardware"),
+ server.ProjectOption(
+ "example_project",
+ help="Type of project to generate.",
+ choices=tuple(PROJECT_TYPES),
+ ),
+ server.ProjectOption(
+ "verbose", help="True to pass --verbose flag to arduino-cli compile and upload"
+ ),
+]
+
+
+class Handler(server.ProjectAPIHandler):
+ def __init__(self):
+ super(Handler, self).__init__()
+ self._proc = None
+ self._port = None
+ self._serial = None
+
+ def server_info_query(self, tvm_version):
+ return server.ServerInfo(
+ platform_name="arduino",
+ is_template=IS_TEMPLATE,
+ model_library_format_path=MODEL_LIBRARY_FORMAT_PATH,
+ project_options=PROJECT_OPTIONS,
+ )
+
+ def _copy_project_files(self, api_server_dir, project_dir, project_type):
+ project_types_folder = api_server_dir.parents[0]
+ shutil.copytree(
+ project_types_folder / project_type / "src", project_dir / "src", dirs_exist_ok=True
+ )
+ # Arduino requires the .ino file have the same filename as its containing folder
+ shutil.copy2(
+ project_types_folder / project_type / "project.ino",
+ project_dir / f"{project_dir.stem}.ino",
+ )
+
+ CRT_COPY_ITEMS = ("include", "src")
+
+ def _copy_standalone_crt(self, source_dir, standalone_crt_dir):
+ # Copy over the standalone_crt directory
+ output_crt_dir = source_dir / "standalone_crt"
+ for item in self.CRT_COPY_ITEMS:
+ src_path = os.path.join(standalone_crt_dir, item)
+ dst_path = output_crt_dir / item
+ if os.path.isdir(src_path):
+ shutil.copytree(src_path, dst_path)
+ else:
+ shutil.copy2(src_path, dst_path)
+
+ # Example project is the "minimum viable project",
+ # and doesn't need a fancy RPC server
+ EXAMPLE_PROJECT_UNUSED_COMPONENTS = [
+ "include/dmlc",
+ "src/support",
+ "src/runtime/minrpc",
+ "src/runtime/crt/graph_executor",
+ "src/runtime/crt/microtvm_rpc_common",
+ "src/runtime/crt/microtvm_rpc_server",
+ "src/runtime/crt/tab",
+ ]
+
+ def _remove_unused_components(self, source_dir, project_type):
+ unused_components = []
+ if project_type == "example_project":
+ unused_components = self.EXAMPLE_PROJECT_UNUSED_COMPONENTS
+
+ for component in unused_components:
+ shutil.rmtree(source_dir / "standalone_crt" / component)
+
+ def _disassemble_mlf(self, mlf_tar_path, source_dir):
+ with tempfile.TemporaryDirectory() as mlf_unpacking_dir_str:
+ mlf_unpacking_dir = pathlib.Path(mlf_unpacking_dir_str)
+ with tarfile.open(mlf_tar_path, "r:") as tar:
+ tar.extractall(mlf_unpacking_dir)
+
+ model_dir = source_dir / "model"
+ model_dir.mkdir()
+
+ # Copy C files from model. The filesnames and quantity
+ # depend on the target string, so we just copy all c files
+ source_dir = mlf_unpacking_dir / "codegen" / "host" / "src"
+ for file in source_dir.rglob(f"*.c"):
+ shutil.copy(file, model_dir)
+
+ # Return metadata.json for use in templating
+ with open(os.path.join(mlf_unpacking_dir, "metadata.json")) as f:
+ metadata = json.load(f)
+ return metadata
+
+ def _template_model_header(self, source_dir, metadata):
+ with open(source_dir / "model.h", "r") as f:
+ model_h_template = Template(f.read())
+
+ template_values = {
+ "workspace_size_bytes": metadata["memory"]["functions"]["main"][0][
Review comment:
do you want to assert anything here? consider looking at "style" metadata keyword too
##########
File path: apps/microtvm/arduino/template_project/microtvm_api_server.py
##########
@@ -0,0 +1,423 @@
+import collections
+import functools
+import json
+import logging
+import os
+import os.path
+import pathlib
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tarfile
+from string import Template
+import tempfile
+import time
+
+import serial
+import serial.tools.list_ports
+
+from tvm.micro.project_api import server
+
+MODEL_LIBRARY_FORMAT_RELPATH = pathlib.Path("src") / "model" / "model.tar"
+API_SERVER_DIR = pathlib.Path(os.path.dirname(__file__) or os.path.getcwd())
+BUILD_DIR = API_SERVER_DIR / "build"
+MODEL_LIBRARY_FORMAT_PATH = API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH
+
+IS_TEMPLATE = not (API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH).exists()
+
+
+class InvalidPortException(Exception):
+ """Raised when the given port could not be opened"""
+
+
+class SketchUploadException(Exception):
+ """Raised when a sketch cannot be uploaded for an unknown reason."""
+
+
+class BoardAutodetectFailed(Exception):
+ """Raised when no attached hardware is found matching the requested board"""
+
+
+BOARD_PROPERTIES = {
+ "due": {"package": "arduino", "architecture": "sam", "board": "arduino_due_x"},
+ # Due to the way the Feather S2 bootloader works, compilation
+ # behaves fine but uploads cannot be done automatically
+ "feathers2": {
+ "package": "esp32",
+ "architecture": "esp32",
+ "board": "feathers2",
+ },
+ # Spresense only works as of its v2.3.0 sdk
+ "spresense": {
+ "package": "SPRESENSE",
+ "architecture": "spresense",
+ "board": "spresense",
+ },
+ "nano33ble": {
+ "package": "arduino",
+ "architecture": "mbed_nano",
+ "board": "nano33ble",
+ },
+ "pybadge": {
+ "package": "adafruit",
+ "architecture": "samd",
+ "board": "adafruit_pybadge_m4",
+ },
+ # The Teensy boards are listed here for completeness, but they
+ # won't work until https://github.com/arduino/arduino-cli/issues/700
+ # is finished
+ "teensy40": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy40",
+ },
+ "teensy41": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy41",
+ },
+}
+
+PROJECT_TYPES = ["example_project", "host_driven"]
+
+PROJECT_OPTIONS = [
+ server.ProjectOption(
+ "arduino_board",
+ choices=list(BOARD_PROPERTIES),
+ help="Name of the Arduino board to build for",
+ ),
+ server.ProjectOption("arduino_cli_cmd", help="Path to the arduino-cli tool."),
+ server.ProjectOption("port", help="Port to use for connecting to hardware"),
+ server.ProjectOption(
+ "example_project",
+ help="Type of project to generate.",
+ choices=tuple(PROJECT_TYPES),
+ ),
+ server.ProjectOption(
+ "verbose", help="True to pass --verbose flag to arduino-cli compile and upload"
+ ),
+]
+
+
+class Handler(server.ProjectAPIHandler):
+ def __init__(self):
+ super(Handler, self).__init__()
+ self._proc = None
+ self._port = None
+ self._serial = None
+
+ def server_info_query(self, tvm_version):
+ return server.ServerInfo(
+ platform_name="arduino",
+ is_template=IS_TEMPLATE,
+ model_library_format_path=MODEL_LIBRARY_FORMAT_PATH,
+ project_options=PROJECT_OPTIONS,
+ )
+
+ def _copy_project_files(self, api_server_dir, project_dir, project_type):
+ project_types_folder = api_server_dir.parents[0]
+ shutil.copytree(
+ project_types_folder / project_type / "src", project_dir / "src", dirs_exist_ok=True
+ )
+ # Arduino requires the .ino file have the same filename as its containing folder
+ shutil.copy2(
+ project_types_folder / project_type / "project.ino",
+ project_dir / f"{project_dir.stem}.ino",
+ )
+
+ CRT_COPY_ITEMS = ("include", "src")
+
+ def _copy_standalone_crt(self, source_dir, standalone_crt_dir):
+ # Copy over the standalone_crt directory
+ output_crt_dir = source_dir / "standalone_crt"
+ for item in self.CRT_COPY_ITEMS:
+ src_path = os.path.join(standalone_crt_dir, item)
+ dst_path = output_crt_dir / item
+ if os.path.isdir(src_path):
+ shutil.copytree(src_path, dst_path)
+ else:
+ shutil.copy2(src_path, dst_path)
+
+ # Example project is the "minimum viable project",
+ # and doesn't need a fancy RPC server
+ EXAMPLE_PROJECT_UNUSED_COMPONENTS = [
+ "include/dmlc",
+ "src/support",
+ "src/runtime/minrpc",
+ "src/runtime/crt/graph_executor",
+ "src/runtime/crt/microtvm_rpc_common",
+ "src/runtime/crt/microtvm_rpc_server",
+ "src/runtime/crt/tab",
+ ]
+
+ def _remove_unused_components(self, source_dir, project_type):
+ unused_components = []
+ if project_type == "example_project":
+ unused_components = self.EXAMPLE_PROJECT_UNUSED_COMPONENTS
+
+ for component in unused_components:
+ shutil.rmtree(source_dir / "standalone_crt" / component)
+
+ def _disassemble_mlf(self, mlf_tar_path, source_dir):
+ with tempfile.TemporaryDirectory() as mlf_unpacking_dir_str:
+ mlf_unpacking_dir = pathlib.Path(mlf_unpacking_dir_str)
+ with tarfile.open(mlf_tar_path, "r:") as tar:
+ tar.extractall(mlf_unpacking_dir)
+
+ model_dir = source_dir / "model"
+ model_dir.mkdir()
+
+ # Copy C files from model. The filesnames and quantity
+ # depend on the target string, so we just copy all c files
+ source_dir = mlf_unpacking_dir / "codegen" / "host" / "src"
+ for file in source_dir.rglob(f"*.c"):
+ shutil.copy(file, model_dir)
+
+ # Return metadata.json for use in templating
+ with open(os.path.join(mlf_unpacking_dir, "metadata.json")) as f:
+ metadata = json.load(f)
+ return metadata
+
+ def _template_model_header(self, source_dir, metadata):
+ with open(source_dir / "model.h", "r") as f:
+ model_h_template = Template(f.read())
+
+ template_values = {
+ "workspace_size_bytes": metadata["memory"]["functions"]["main"][0][
+ "workspace_size_bytes"
+ ],
+ }
+
+ with open(source_dir / "model.h", "w") as f:
+ f.write(model_h_template.substitute(template_values))
+
+ # Arduino ONLY recognizes .ino, .ccp, .c, .h
+
+ CPP_FILE_EXTENSION_SYNONYMS = ("cc", "cxx")
+
+ def _change_cpp_file_extensions(self, source_dir):
+ for ext in self.CPP_FILE_EXTENSION_SYNONYMS:
+ for filename in source_dir.rglob(f"*.{ext}"):
+ filename.rename(filename.with_suffix(".cpp"))
+
+ for filename in source_dir.rglob(f"*.inc"):
+ filename.rename(filename.with_suffix(".h"))
+
+ def _process_autogenerated_inc_files(self, source_dir):
+ for filename in source_dir.rglob(f"*.inc"):
+ # Individual file fixes
+ if filename.stem == "gentab_ccitt":
+ with open(filename, "r+") as f:
+ content = f.read()
+ f.seek(0, 0)
+ f.write('#include "inttypes.h"\n' + content)
+
+ filename.rename(filename.with_suffix(".c"))
+
+ POSSIBLE_BASE_PATHS = ["src/standalone_crt/include/", "src/standalone_crt/crt_config/"]
+
+ def _find_modified_include_path(self, project_dir, file_path, import_path):
+ # If the import is for a .inc file we renamed to .c earlier, fix it
Review comment:
explain "fix it"
##########
File path: apps/microtvm/arduino/template_project/microtvm_api_server.py
##########
@@ -0,0 +1,423 @@
+import collections
+import functools
+import json
+import logging
+import os
+import os.path
+import pathlib
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tarfile
+from string import Template
+import tempfile
+import time
+
+import serial
+import serial.tools.list_ports
+
+from tvm.micro.project_api import server
+
+MODEL_LIBRARY_FORMAT_RELPATH = pathlib.Path("src") / "model" / "model.tar"
+API_SERVER_DIR = pathlib.Path(os.path.dirname(__file__) or os.path.getcwd())
+BUILD_DIR = API_SERVER_DIR / "build"
+MODEL_LIBRARY_FORMAT_PATH = API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH
+
+IS_TEMPLATE = not (API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH).exists()
+
+
+class InvalidPortException(Exception):
+ """Raised when the given port could not be opened"""
+
+
+class SketchUploadException(Exception):
+ """Raised when a sketch cannot be uploaded for an unknown reason."""
+
+
+class BoardAutodetectFailed(Exception):
+ """Raised when no attached hardware is found matching the requested board"""
+
+
+BOARD_PROPERTIES = {
+ "due": {"package": "arduino", "architecture": "sam", "board": "arduino_due_x"},
+ # Due to the way the Feather S2 bootloader works, compilation
+ # behaves fine but uploads cannot be done automatically
+ "feathers2": {
+ "package": "esp32",
+ "architecture": "esp32",
+ "board": "feathers2",
+ },
+ # Spresense only works as of its v2.3.0 sdk
+ "spresense": {
+ "package": "SPRESENSE",
+ "architecture": "spresense",
+ "board": "spresense",
+ },
+ "nano33ble": {
+ "package": "arduino",
+ "architecture": "mbed_nano",
+ "board": "nano33ble",
+ },
+ "pybadge": {
+ "package": "adafruit",
+ "architecture": "samd",
+ "board": "adafruit_pybadge_m4",
+ },
+ # The Teensy boards are listed here for completeness, but they
+ # won't work until https://github.com/arduino/arduino-cli/issues/700
+ # is finished
+ "teensy40": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy40",
+ },
+ "teensy41": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy41",
+ },
+}
+
+PROJECT_TYPES = ["example_project", "host_driven"]
+
+PROJECT_OPTIONS = [
+ server.ProjectOption(
+ "arduino_board",
+ choices=list(BOARD_PROPERTIES),
+ help="Name of the Arduino board to build for",
+ ),
+ server.ProjectOption("arduino_cli_cmd", help="Path to the arduino-cli tool."),
+ server.ProjectOption("port", help="Port to use for connecting to hardware"),
+ server.ProjectOption(
+ "example_project",
+ help="Type of project to generate.",
+ choices=tuple(PROJECT_TYPES),
+ ),
+ server.ProjectOption(
+ "verbose", help="True to pass --verbose flag to arduino-cli compile and upload"
+ ),
+]
+
+
+class Handler(server.ProjectAPIHandler):
+ def __init__(self):
+ super(Handler, self).__init__()
+ self._proc = None
+ self._port = None
+ self._serial = None
+
+ def server_info_query(self, tvm_version):
+ return server.ServerInfo(
+ platform_name="arduino",
+ is_template=IS_TEMPLATE,
+ model_library_format_path=MODEL_LIBRARY_FORMAT_PATH,
+ project_options=PROJECT_OPTIONS,
+ )
+
+ def _copy_project_files(self, api_server_dir, project_dir, project_type):
+ project_types_folder = api_server_dir.parents[0]
+ shutil.copytree(
+ project_types_folder / project_type / "src", project_dir / "src", dirs_exist_ok=True
+ )
+ # Arduino requires the .ino file have the same filename as its containing folder
+ shutil.copy2(
+ project_types_folder / project_type / "project.ino",
+ project_dir / f"{project_dir.stem}.ino",
+ )
+
+ CRT_COPY_ITEMS = ("include", "src")
+
+ def _copy_standalone_crt(self, source_dir, standalone_crt_dir):
+ # Copy over the standalone_crt directory
+ output_crt_dir = source_dir / "standalone_crt"
+ for item in self.CRT_COPY_ITEMS:
+ src_path = os.path.join(standalone_crt_dir, item)
+ dst_path = output_crt_dir / item
+ if os.path.isdir(src_path):
+ shutil.copytree(src_path, dst_path)
+ else:
+ shutil.copy2(src_path, dst_path)
+
+ # Example project is the "minimum viable project",
+ # and doesn't need a fancy RPC server
+ EXAMPLE_PROJECT_UNUSED_COMPONENTS = [
+ "include/dmlc",
+ "src/support",
+ "src/runtime/minrpc",
+ "src/runtime/crt/graph_executor",
+ "src/runtime/crt/microtvm_rpc_common",
+ "src/runtime/crt/microtvm_rpc_server",
+ "src/runtime/crt/tab",
+ ]
+
+ def _remove_unused_components(self, source_dir, project_type):
+ unused_components = []
+ if project_type == "example_project":
+ unused_components = self.EXAMPLE_PROJECT_UNUSED_COMPONENTS
+
+ for component in unused_components:
+ shutil.rmtree(source_dir / "standalone_crt" / component)
+
+ def _disassemble_mlf(self, mlf_tar_path, source_dir):
+ with tempfile.TemporaryDirectory() as mlf_unpacking_dir_str:
+ mlf_unpacking_dir = pathlib.Path(mlf_unpacking_dir_str)
+ with tarfile.open(mlf_tar_path, "r:") as tar:
+ tar.extractall(mlf_unpacking_dir)
+
+ model_dir = source_dir / "model"
+ model_dir.mkdir()
+
+ # Copy C files from model. The filesnames and quantity
+ # depend on the target string, so we just copy all c files
+ source_dir = mlf_unpacking_dir / "codegen" / "host" / "src"
+ for file in source_dir.rglob(f"*.c"):
+ shutil.copy(file, model_dir)
+
+ # Return metadata.json for use in templating
+ with open(os.path.join(mlf_unpacking_dir, "metadata.json")) as f:
+ metadata = json.load(f)
+ return metadata
+
+ def _template_model_header(self, source_dir, metadata):
+ with open(source_dir / "model.h", "r") as f:
+ model_h_template = Template(f.read())
+
+ template_values = {
+ "workspace_size_bytes": metadata["memory"]["functions"]["main"][0][
+ "workspace_size_bytes"
+ ],
+ }
+
+ with open(source_dir / "model.h", "w") as f:
+ f.write(model_h_template.substitute(template_values))
+
+ # Arduino ONLY recognizes .ino, .ccp, .c, .h
+
+ CPP_FILE_EXTENSION_SYNONYMS = ("cc", "cxx")
+
+ def _change_cpp_file_extensions(self, source_dir):
+ for ext in self.CPP_FILE_EXTENSION_SYNONYMS:
+ for filename in source_dir.rglob(f"*.{ext}"):
+ filename.rename(filename.with_suffix(".cpp"))
+
+ for filename in source_dir.rglob(f"*.inc"):
+ filename.rename(filename.with_suffix(".h"))
+
+ def _process_autogenerated_inc_files(self, source_dir):
+ for filename in source_dir.rglob(f"*.inc"):
+ # Individual file fixes
+ if filename.stem == "gentab_ccitt":
+ with open(filename, "r+") as f:
+ content = f.read()
+ f.seek(0, 0)
+ f.write('#include "inttypes.h"\n' + content)
+
+ filename.rename(filename.with_suffix(".c"))
+
+ POSSIBLE_BASE_PATHS = ["src/standalone_crt/include/", "src/standalone_crt/crt_config/"]
+
+ def _find_modified_include_path(self, project_dir, file_path, import_path):
+ # If the import is for a .inc file we renamed to .c earlier, fix it
+ if import_path.endswith(self.CPP_FILE_EXTENSION_SYNONYMS):
+ import_path = re.sub(r"\.[a-z]+$", ".cpp", import_path)
+
+ if import_path.endswith(".inc"):
+ import_path = re.sub(r"\.[a-z]+$", ".h", import_path)
+
+ # If the import already works, don't modify it
+ if (file_path.parents[0] / import_path).exists():
+ return import_path
+
+ relative_path = file_path.relative_to(project_dir)
+ up_dirs_path = "../" * str(relative_path).count("/")
+
+ for base_path in self.POSSIBLE_BASE_PATHS:
+ full_potential_path = project_dir / base_path / import_path
+ if full_potential_path.exists():
+ new_include = up_dirs_path + base_path + import_path
+ return new_include
+
+ # If we can't find the file, just leave it untouched
+ # It's probably a standard C/C++ header
+ return import_path
+
+ # Arduino only supports imports relative to the top-level project,
+ # so we need to adjust each import to meet this convention
+ def _convert_imports(self, project_dir, source_dir):
+ for ext in ("c", "h", "cpp"):
+ for filename in source_dir.rglob(f"*.{ext}"):
+ with filename.open() as file:
+ lines = file.readlines()
+
+ for i in range(len(lines)):
+ # Check if line has an include
+ result = re.search(r"#include\s*[<\"]([^>]*)[>\"]", lines[i])
+ if not result:
+ continue
+ new_include = self._find_modified_include_path(
+ project_dir, filename, result.groups()[0]
+ )
+
+ lines[i] = f'#include "{new_include}"\n'
+
+ with filename.open("w") as file:
+ file.writelines(lines)
+
+ def generate_project(self, model_library_format_path, standalone_crt_dir, project_dir, options):
+ # Reference key directories with pathlib
+ project_dir = pathlib.Path(project_dir)
+ project_dir.mkdir()
+ source_dir = project_dir / "src"
+ source_dir.mkdir()
+
+ # Copies files from the template folder to project_dir. model.h is copied here,
+ # but will also need to be templated later.
+ if IS_TEMPLATE:
Review comment:
technically unneeded--can assume TVM won't call generate_project unless it's a template
##########
File path: apps/microtvm/arduino/template_project/microtvm_api_server.py
##########
@@ -0,0 +1,423 @@
+import collections
+import functools
+import json
+import logging
+import os
+import os.path
+import pathlib
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tarfile
+from string import Template
+import tempfile
+import time
+
+import serial
+import serial.tools.list_ports
+
+from tvm.micro.project_api import server
+
+MODEL_LIBRARY_FORMAT_RELPATH = pathlib.Path("src") / "model" / "model.tar"
+API_SERVER_DIR = pathlib.Path(os.path.dirname(__file__) or os.path.getcwd())
+BUILD_DIR = API_SERVER_DIR / "build"
+MODEL_LIBRARY_FORMAT_PATH = API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH
+
+IS_TEMPLATE = not (API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH).exists()
+
+
+class InvalidPortException(Exception):
+ """Raised when the given port could not be opened"""
+
+
+class SketchUploadException(Exception):
+ """Raised when a sketch cannot be uploaded for an unknown reason."""
+
+
+class BoardAutodetectFailed(Exception):
+ """Raised when no attached hardware is found matching the requested board"""
+
+
+BOARD_PROPERTIES = {
+ "due": {"package": "arduino", "architecture": "sam", "board": "arduino_due_x"},
+ # Due to the way the Feather S2 bootloader works, compilation
+ # behaves fine but uploads cannot be done automatically
+ "feathers2": {
+ "package": "esp32",
+ "architecture": "esp32",
+ "board": "feathers2",
+ },
+ # Spresense only works as of its v2.3.0 sdk
+ "spresense": {
+ "package": "SPRESENSE",
+ "architecture": "spresense",
+ "board": "spresense",
+ },
+ "nano33ble": {
+ "package": "arduino",
+ "architecture": "mbed_nano",
+ "board": "nano33ble",
+ },
+ "pybadge": {
+ "package": "adafruit",
+ "architecture": "samd",
+ "board": "adafruit_pybadge_m4",
+ },
+ # The Teensy boards are listed here for completeness, but they
+ # won't work until https://github.com/arduino/arduino-cli/issues/700
+ # is finished
+ "teensy40": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy40",
+ },
+ "teensy41": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy41",
+ },
+}
+
+PROJECT_TYPES = ["example_project", "host_driven"]
+
+PROJECT_OPTIONS = [
+ server.ProjectOption(
+ "arduino_board",
+ choices=list(BOARD_PROPERTIES),
+ help="Name of the Arduino board to build for",
+ ),
+ server.ProjectOption("arduino_cli_cmd", help="Path to the arduino-cli tool."),
+ server.ProjectOption("port", help="Port to use for connecting to hardware"),
+ server.ProjectOption(
+ "example_project",
+ help="Type of project to generate.",
+ choices=tuple(PROJECT_TYPES),
+ ),
+ server.ProjectOption(
+ "verbose", help="True to pass --verbose flag to arduino-cli compile and upload"
+ ),
+]
+
+
+class Handler(server.ProjectAPIHandler):
+ def __init__(self):
+ super(Handler, self).__init__()
+ self._proc = None
+ self._port = None
+ self._serial = None
+
+ def server_info_query(self, tvm_version):
+ return server.ServerInfo(
+ platform_name="arduino",
+ is_template=IS_TEMPLATE,
+ model_library_format_path=MODEL_LIBRARY_FORMAT_PATH,
+ project_options=PROJECT_OPTIONS,
+ )
+
+ def _copy_project_files(self, api_server_dir, project_dir, project_type):
+ project_types_folder = api_server_dir.parents[0]
+ shutil.copytree(
+ project_types_folder / project_type / "src", project_dir / "src", dirs_exist_ok=True
+ )
+ # Arduino requires the .ino file have the same filename as its containing folder
+ shutil.copy2(
+ project_types_folder / project_type / "project.ino",
+ project_dir / f"{project_dir.stem}.ino",
+ )
+
+ CRT_COPY_ITEMS = ("include", "src")
+
+ def _copy_standalone_crt(self, source_dir, standalone_crt_dir):
+ # Copy over the standalone_crt directory
+ output_crt_dir = source_dir / "standalone_crt"
+ for item in self.CRT_COPY_ITEMS:
+ src_path = os.path.join(standalone_crt_dir, item)
+ dst_path = output_crt_dir / item
+ if os.path.isdir(src_path):
+ shutil.copytree(src_path, dst_path)
+ else:
+ shutil.copy2(src_path, dst_path)
+
+ # Example project is the "minimum viable project",
+ # and doesn't need a fancy RPC server
+ EXAMPLE_PROJECT_UNUSED_COMPONENTS = [
+ "include/dmlc",
+ "src/support",
+ "src/runtime/minrpc",
+ "src/runtime/crt/graph_executor",
+ "src/runtime/crt/microtvm_rpc_common",
+ "src/runtime/crt/microtvm_rpc_server",
+ "src/runtime/crt/tab",
+ ]
+
+ def _remove_unused_components(self, source_dir, project_type):
+ unused_components = []
+ if project_type == "example_project":
+ unused_components = self.EXAMPLE_PROJECT_UNUSED_COMPONENTS
+
+ for component in unused_components:
+ shutil.rmtree(source_dir / "standalone_crt" / component)
+
+ def _disassemble_mlf(self, mlf_tar_path, source_dir):
+ with tempfile.TemporaryDirectory() as mlf_unpacking_dir_str:
+ mlf_unpacking_dir = pathlib.Path(mlf_unpacking_dir_str)
+ with tarfile.open(mlf_tar_path, "r:") as tar:
+ tar.extractall(mlf_unpacking_dir)
+
+ model_dir = source_dir / "model"
+ model_dir.mkdir()
+
+ # Copy C files from model. The filesnames and quantity
+ # depend on the target string, so we just copy all c files
+ source_dir = mlf_unpacking_dir / "codegen" / "host" / "src"
+ for file in source_dir.rglob(f"*.c"):
+ shutil.copy(file, model_dir)
+
+ # Return metadata.json for use in templating
+ with open(os.path.join(mlf_unpacking_dir, "metadata.json")) as f:
+ metadata = json.load(f)
+ return metadata
+
+ def _template_model_header(self, source_dir, metadata):
+ with open(source_dir / "model.h", "r") as f:
+ model_h_template = Template(f.read())
+
+ template_values = {
+ "workspace_size_bytes": metadata["memory"]["functions"]["main"][0][
+ "workspace_size_bytes"
+ ],
+ }
+
+ with open(source_dir / "model.h", "w") as f:
+ f.write(model_h_template.substitute(template_values))
+
+ # Arduino ONLY recognizes .ino, .ccp, .c, .h
+
+ CPP_FILE_EXTENSION_SYNONYMS = ("cc", "cxx")
+
+ def _change_cpp_file_extensions(self, source_dir):
+ for ext in self.CPP_FILE_EXTENSION_SYNONYMS:
+ for filename in source_dir.rglob(f"*.{ext}"):
+ filename.rename(filename.with_suffix(".cpp"))
+
+ for filename in source_dir.rglob(f"*.inc"):
+ filename.rename(filename.with_suffix(".h"))
+
+ def _process_autogenerated_inc_files(self, source_dir):
+ for filename in source_dir.rglob(f"*.inc"):
+ # Individual file fixes
+ if filename.stem == "gentab_ccitt":
+ with open(filename, "r+") as f:
+ content = f.read()
+ f.seek(0, 0)
+ f.write('#include "inttypes.h"\n' + content)
+
+ filename.rename(filename.with_suffix(".c"))
+
+ POSSIBLE_BASE_PATHS = ["src/standalone_crt/include/", "src/standalone_crt/crt_config/"]
+
+ def _find_modified_include_path(self, project_dir, file_path, import_path):
+ # If the import is for a .inc file we renamed to .c earlier, fix it
+ if import_path.endswith(self.CPP_FILE_EXTENSION_SYNONYMS):
+ import_path = re.sub(r"\.[a-z]+$", ".cpp", import_path)
+
+ if import_path.endswith(".inc"):
+ import_path = re.sub(r"\.[a-z]+$", ".h", import_path)
+
+ # If the import already works, don't modify it
+ if (file_path.parents[0] / import_path).exists():
+ return import_path
+
+ relative_path = file_path.relative_to(project_dir)
+ up_dirs_path = "../" * str(relative_path).count("/")
+
+ for base_path in self.POSSIBLE_BASE_PATHS:
+ full_potential_path = project_dir / base_path / import_path
+ if full_potential_path.exists():
+ new_include = up_dirs_path + base_path + import_path
+ return new_include
+
+ # If we can't find the file, just leave it untouched
+ # It's probably a standard C/C++ header
+ return import_path
+
+ # Arduino only supports imports relative to the top-level project,
+ # so we need to adjust each import to meet this convention
+ def _convert_imports(self, project_dir, source_dir):
+ for ext in ("c", "h", "cpp"):
+ for filename in source_dir.rglob(f"*.{ext}"):
+ with filename.open() as file:
+ lines = file.readlines()
+
+ for i in range(len(lines)):
+ # Check if line has an include
+ result = re.search(r"#include\s*[<\"]([^>]*)[>\"]", lines[i])
+ if not result:
+ continue
+ new_include = self._find_modified_include_path(
+ project_dir, filename, result.groups()[0]
+ )
+
+ lines[i] = f'#include "{new_include}"\n'
+
+ with filename.open("w") as file:
+ file.writelines(lines)
+
+ def generate_project(self, model_library_format_path, standalone_crt_dir, project_dir, options):
+ # Reference key directories with pathlib
+ project_dir = pathlib.Path(project_dir)
+ project_dir.mkdir()
+ source_dir = project_dir / "src"
+ source_dir.mkdir()
+
+ # Copies files from the template folder to project_dir. model.h is copied here,
+ # but will also need to be templated later.
+ if IS_TEMPLATE:
+ shutil.copy2(API_SERVER_DIR / "microtvm_api_server.py", project_dir)
+ self._copy_project_files(API_SERVER_DIR, project_dir, options["project_type"])
+
+ # Copy standalone_crt into src folder
+ self._copy_standalone_crt(source_dir, standalone_crt_dir)
+ self._remove_unused_components(source_dir, options["project_type"])
+
+ # Unpack the MLF and copy the relevant files
+ metadata = self._disassemble_mlf(model_library_format_path, source_dir)
+ shutil.copy2(model_library_format_path, source_dir / "model")
+
+ # For AOT, template model.h with metadata to minimize space usage
+ if options["project_type"] == "example_project":
+ self._template_model_header(source_dir, metadata)
+
+ self._change_cpp_file_extensions(source_dir)
+
+ # Recursively change imports
+ self._convert_imports(project_dir, source_dir)
+
+ def _get_fqbn(self, options):
+ o = BOARD_PROPERTIES[options["arduino_board"]]
+ return f"{o['package']}:{o['architecture']}:{o['board']}"
+
+ def build(self, options):
+ BUILD_DIR.mkdir()
+ print(BUILD_DIR)
+
+ compile_cmd = [
+ options["arduino_cli_cmd"],
+ "compile",
+ "./project/",
+ "--fqbn",
+ self._get_fqbn(options),
+ "--build-path",
+ BUILD_DIR.resolve(),
+ ]
+
+ if options.get("verbose"):
+ compile_cmd.append("--verbose")
+
+ # Specify project to compile
+ output = subprocess.check_call(compile_cmd)
+ assert output == 0
+
+ # We run the command `arduino-cli board list`, which produces
+ # outputs of the form:
+ """
+ Port Type Board Name FQBN Core
+ /dev/ttyS4 Serial Port Unknown
+ /dev/ttyUSB0 Serial Port (USB) Spresense SPRESENSE:spresense:spresense SPRESENSE:spresense
+ """
+
+ def _auto_detect_port(self, options):
+ list_cmd = [options["arduino_cli_cmd"], "board", "list"]
+ list_cmd_output = subprocess.check_output(list_cmd).decode("utf-8")
+ # Remove header and new lines at bottom
+ port_options = list_cmd_output.split("\n")[1:-2]
+
+ # Select the first compatible board
+ fqbn = self._get_fqbn(options)
+ for port_option in port_options:
+ if fqbn in port_option:
+ return port_option.split(" ")[0]
+
+ # If no compatible boards, raise an error
+ raise BoardAutodetectFailed()
+
+ def _get_arduino_port(self, options):
+ if not self._port:
+ if "port" in options and options["port"]:
+ self._port = options["port"]
+ else:
+ self._port = self._auto_detect_port(options)
+
+ return self._port
+
+ def flash(self, options):
+ port = self._get_arduino_port(options)
+
+ upload_cmd = [
+ options["arduino_cli_cmd"],
+ "upload",
+ "./project",
+ "--fqbn",
+ self._get_fqbn(options),
+ "--input-dir",
+ BUILD_DIR.resolve(),
+ "--port",
+ port,
+ ]
+
+ if options.get("verbose"):
+ upload_cmd.append("--verbose")
+
+ output = subprocess.check_call(upload_cmd)
+
+ if output == 2:
+ raise InvalidPortException()
+ elif output > 0:
+ raise SketchUploadException()
+
+ def open_transport(self, options):
+ # Zephyr example doesn't throw an error in this case
+ if self._serial is not None:
+ return
+
+ port = self._get_arduino_port(options)
+
+ # Wait for port to become available
Review comment:
comment why
##########
File path: apps/microtvm/arduino/template_project/microtvm_api_server.py
##########
@@ -0,0 +1,423 @@
+import collections
+import functools
+import json
+import logging
+import os
+import os.path
+import pathlib
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tarfile
+from string import Template
+import tempfile
+import time
+
+import serial
+import serial.tools.list_ports
+
+from tvm.micro.project_api import server
+
+MODEL_LIBRARY_FORMAT_RELPATH = pathlib.Path("src") / "model" / "model.tar"
+API_SERVER_DIR = pathlib.Path(os.path.dirname(__file__) or os.path.getcwd())
+BUILD_DIR = API_SERVER_DIR / "build"
+MODEL_LIBRARY_FORMAT_PATH = API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH
+
+IS_TEMPLATE = not (API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH).exists()
+
+
+class InvalidPortException(Exception):
+ """Raised when the given port could not be opened"""
+
+
+class SketchUploadException(Exception):
+ """Raised when a sketch cannot be uploaded for an unknown reason."""
+
+
+class BoardAutodetectFailed(Exception):
+ """Raised when no attached hardware is found matching the requested board"""
+
+
+BOARD_PROPERTIES = {
+ "due": {"package": "arduino", "architecture": "sam", "board": "arduino_due_x"},
+ # Due to the way the Feather S2 bootloader works, compilation
+ # behaves fine but uploads cannot be done automatically
+ "feathers2": {
+ "package": "esp32",
+ "architecture": "esp32",
+ "board": "feathers2",
+ },
+ # Spresense only works as of its v2.3.0 sdk
+ "spresense": {
+ "package": "SPRESENSE",
+ "architecture": "spresense",
+ "board": "spresense",
+ },
+ "nano33ble": {
+ "package": "arduino",
+ "architecture": "mbed_nano",
+ "board": "nano33ble",
+ },
+ "pybadge": {
+ "package": "adafruit",
+ "architecture": "samd",
+ "board": "adafruit_pybadge_m4",
+ },
+ # The Teensy boards are listed here for completeness, but they
+ # won't work until https://github.com/arduino/arduino-cli/issues/700
+ # is finished
+ "teensy40": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy40",
+ },
+ "teensy41": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy41",
+ },
+}
+
+PROJECT_TYPES = ["example_project", "host_driven"]
+
+PROJECT_OPTIONS = [
+ server.ProjectOption(
+ "arduino_board",
+ choices=list(BOARD_PROPERTIES),
+ help="Name of the Arduino board to build for",
+ ),
+ server.ProjectOption("arduino_cli_cmd", help="Path to the arduino-cli tool."),
+ server.ProjectOption("port", help="Port to use for connecting to hardware"),
+ server.ProjectOption(
+ "example_project",
+ help="Type of project to generate.",
+ choices=tuple(PROJECT_TYPES),
+ ),
+ server.ProjectOption(
+ "verbose", help="True to pass --verbose flag to arduino-cli compile and upload"
+ ),
+]
+
+
+class Handler(server.ProjectAPIHandler):
+ def __init__(self):
+ super(Handler, self).__init__()
+ self._proc = None
+ self._port = None
+ self._serial = None
+
+ def server_info_query(self, tvm_version):
+ return server.ServerInfo(
+ platform_name="arduino",
+ is_template=IS_TEMPLATE,
+ model_library_format_path=MODEL_LIBRARY_FORMAT_PATH,
+ project_options=PROJECT_OPTIONS,
+ )
+
+ def _copy_project_files(self, api_server_dir, project_dir, project_type):
+ project_types_folder = api_server_dir.parents[0]
+ shutil.copytree(
+ project_types_folder / project_type / "src", project_dir / "src", dirs_exist_ok=True
+ )
+ # Arduino requires the .ino file have the same filename as its containing folder
+ shutil.copy2(
+ project_types_folder / project_type / "project.ino",
+ project_dir / f"{project_dir.stem}.ino",
+ )
+
+ CRT_COPY_ITEMS = ("include", "src")
+
+ def _copy_standalone_crt(self, source_dir, standalone_crt_dir):
+ # Copy over the standalone_crt directory
+ output_crt_dir = source_dir / "standalone_crt"
+ for item in self.CRT_COPY_ITEMS:
+ src_path = os.path.join(standalone_crt_dir, item)
+ dst_path = output_crt_dir / item
+ if os.path.isdir(src_path):
+ shutil.copytree(src_path, dst_path)
+ else:
+ shutil.copy2(src_path, dst_path)
+
+ # Example project is the "minimum viable project",
+ # and doesn't need a fancy RPC server
+ EXAMPLE_PROJECT_UNUSED_COMPONENTS = [
+ "include/dmlc",
+ "src/support",
+ "src/runtime/minrpc",
+ "src/runtime/crt/graph_executor",
+ "src/runtime/crt/microtvm_rpc_common",
+ "src/runtime/crt/microtvm_rpc_server",
+ "src/runtime/crt/tab",
+ ]
+
+ def _remove_unused_components(self, source_dir, project_type):
+ unused_components = []
+ if project_type == "example_project":
+ unused_components = self.EXAMPLE_PROJECT_UNUSED_COMPONENTS
+
+ for component in unused_components:
+ shutil.rmtree(source_dir / "standalone_crt" / component)
+
+ def _disassemble_mlf(self, mlf_tar_path, source_dir):
+ with tempfile.TemporaryDirectory() as mlf_unpacking_dir_str:
+ mlf_unpacking_dir = pathlib.Path(mlf_unpacking_dir_str)
+ with tarfile.open(mlf_tar_path, "r:") as tar:
+ tar.extractall(mlf_unpacking_dir)
+
+ model_dir = source_dir / "model"
+ model_dir.mkdir()
+
+ # Copy C files from model. The filesnames and quantity
+ # depend on the target string, so we just copy all c files
+ source_dir = mlf_unpacking_dir / "codegen" / "host" / "src"
+ for file in source_dir.rglob(f"*.c"):
+ shutil.copy(file, model_dir)
+
+ # Return metadata.json for use in templating
+ with open(os.path.join(mlf_unpacking_dir, "metadata.json")) as f:
+ metadata = json.load(f)
+ return metadata
+
+ def _template_model_header(self, source_dir, metadata):
+ with open(source_dir / "model.h", "r") as f:
+ model_h_template = Template(f.read())
+
+ template_values = {
+ "workspace_size_bytes": metadata["memory"]["functions"]["main"][0][
+ "workspace_size_bytes"
+ ],
+ }
+
+ with open(source_dir / "model.h", "w") as f:
+ f.write(model_h_template.substitute(template_values))
+
+ # Arduino ONLY recognizes .ino, .ccp, .c, .h
+
+ CPP_FILE_EXTENSION_SYNONYMS = ("cc", "cxx")
+
+ def _change_cpp_file_extensions(self, source_dir):
+ for ext in self.CPP_FILE_EXTENSION_SYNONYMS:
+ for filename in source_dir.rglob(f"*.{ext}"):
+ filename.rename(filename.with_suffix(".cpp"))
+
+ for filename in source_dir.rglob(f"*.inc"):
+ filename.rename(filename.with_suffix(".h"))
+
+ def _process_autogenerated_inc_files(self, source_dir):
+ for filename in source_dir.rglob(f"*.inc"):
+ # Individual file fixes
+ if filename.stem == "gentab_ccitt":
+ with open(filename, "r+") as f:
+ content = f.read()
+ f.seek(0, 0)
+ f.write('#include "inttypes.h"\n' + content)
+
+ filename.rename(filename.with_suffix(".c"))
+
+ POSSIBLE_BASE_PATHS = ["src/standalone_crt/include/", "src/standalone_crt/crt_config/"]
+
+ def _find_modified_include_path(self, project_dir, file_path, import_path):
+ # If the import is for a .inc file we renamed to .c earlier, fix it
+ if import_path.endswith(self.CPP_FILE_EXTENSION_SYNONYMS):
+ import_path = re.sub(r"\.[a-z]+$", ".cpp", import_path)
+
+ if import_path.endswith(".inc"):
+ import_path = re.sub(r"\.[a-z]+$", ".h", import_path)
+
+ # If the import already works, don't modify it
+ if (file_path.parents[0] / import_path).exists():
+ return import_path
+
+ relative_path = file_path.relative_to(project_dir)
+ up_dirs_path = "../" * str(relative_path).count("/")
+
+ for base_path in self.POSSIBLE_BASE_PATHS:
+ full_potential_path = project_dir / base_path / import_path
+ if full_potential_path.exists():
+ new_include = up_dirs_path + base_path + import_path
+ return new_include
+
+ # If we can't find the file, just leave it untouched
+ # It's probably a standard C/C++ header
+ return import_path
+
+ # Arduino only supports imports relative to the top-level project,
+ # so we need to adjust each import to meet this convention
+ def _convert_imports(self, project_dir, source_dir):
+ for ext in ("c", "h", "cpp"):
+ for filename in source_dir.rglob(f"*.{ext}"):
+ with filename.open() as file:
+ lines = file.readlines()
+
+ for i in range(len(lines)):
+ # Check if line has an include
+ result = re.search(r"#include\s*[<\"]([^>]*)[>\"]", lines[i])
+ if not result:
+ continue
+ new_include = self._find_modified_include_path(
+ project_dir, filename, result.groups()[0]
+ )
+
+ lines[i] = f'#include "{new_include}"\n'
+
+ with filename.open("w") as file:
+ file.writelines(lines)
+
+ def generate_project(self, model_library_format_path, standalone_crt_dir, project_dir, options):
+ # Reference key directories with pathlib
+ project_dir = pathlib.Path(project_dir)
+ project_dir.mkdir()
+ source_dir = project_dir / "src"
+ source_dir.mkdir()
+
+ # Copies files from the template folder to project_dir. model.h is copied here,
+ # but will also need to be templated later.
+ if IS_TEMPLATE:
+ shutil.copy2(API_SERVER_DIR / "microtvm_api_server.py", project_dir)
+ self._copy_project_files(API_SERVER_DIR, project_dir, options["project_type"])
+
+ # Copy standalone_crt into src folder
+ self._copy_standalone_crt(source_dir, standalone_crt_dir)
+ self._remove_unused_components(source_dir, options["project_type"])
+
+ # Unpack the MLF and copy the relevant files
+ metadata = self._disassemble_mlf(model_library_format_path, source_dir)
+ shutil.copy2(model_library_format_path, source_dir / "model")
+
+ # For AOT, template model.h with metadata to minimize space usage
+ if options["project_type"] == "example_project":
+ self._template_model_header(source_dir, metadata)
+
+ self._change_cpp_file_extensions(source_dir)
+
+ # Recursively change imports
+ self._convert_imports(project_dir, source_dir)
+
+ def _get_fqbn(self, options):
+ o = BOARD_PROPERTIES[options["arduino_board"]]
+ return f"{o['package']}:{o['architecture']}:{o['board']}"
+
+ def build(self, options):
+ BUILD_DIR.mkdir()
+ print(BUILD_DIR)
+
+ compile_cmd = [
+ options["arduino_cli_cmd"],
+ "compile",
+ "./project/",
+ "--fqbn",
+ self._get_fqbn(options),
+ "--build-path",
+ BUILD_DIR.resolve(),
+ ]
+
+ if options.get("verbose"):
+ compile_cmd.append("--verbose")
+
+ # Specify project to compile
+ output = subprocess.check_call(compile_cmd)
+ assert output == 0
+
+ # We run the command `arduino-cli board list`, which produces
+ # outputs of the form:
+ """
Review comment:
nit: make this a comment unless there's a good reason it should be a str
##########
File path: apps/microtvm/arduino/template_project/microtvm_api_server.py
##########
@@ -0,0 +1,423 @@
+import collections
+import functools
+import json
+import logging
+import os
+import os.path
+import pathlib
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tarfile
+from string import Template
+import tempfile
+import time
+
+import serial
+import serial.tools.list_ports
+
+from tvm.micro.project_api import server
+
+MODEL_LIBRARY_FORMAT_RELPATH = pathlib.Path("src") / "model" / "model.tar"
+API_SERVER_DIR = pathlib.Path(os.path.dirname(__file__) or os.path.getcwd())
+BUILD_DIR = API_SERVER_DIR / "build"
+MODEL_LIBRARY_FORMAT_PATH = API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH
+
+IS_TEMPLATE = not (API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH).exists()
+
+
+class InvalidPortException(Exception):
+ """Raised when the given port could not be opened"""
+
+
+class SketchUploadException(Exception):
+ """Raised when a sketch cannot be uploaded for an unknown reason."""
+
+
+class BoardAutodetectFailed(Exception):
+ """Raised when no attached hardware is found matching the requested board"""
+
+
+BOARD_PROPERTIES = {
+ "due": {"package": "arduino", "architecture": "sam", "board": "arduino_due_x"},
+ # Due to the way the Feather S2 bootloader works, compilation
+ # behaves fine but uploads cannot be done automatically
+ "feathers2": {
+ "package": "esp32",
+ "architecture": "esp32",
+ "board": "feathers2",
+ },
+ # Spresense only works as of its v2.3.0 sdk
+ "spresense": {
+ "package": "SPRESENSE",
+ "architecture": "spresense",
+ "board": "spresense",
+ },
+ "nano33ble": {
+ "package": "arduino",
+ "architecture": "mbed_nano",
+ "board": "nano33ble",
+ },
+ "pybadge": {
+ "package": "adafruit",
+ "architecture": "samd",
+ "board": "adafruit_pybadge_m4",
+ },
+ # The Teensy boards are listed here for completeness, but they
+ # won't work until https://github.com/arduino/arduino-cli/issues/700
+ # is finished
+ "teensy40": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy40",
+ },
+ "teensy41": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy41",
+ },
+}
+
+PROJECT_TYPES = ["example_project", "host_driven"]
+
+PROJECT_OPTIONS = [
+ server.ProjectOption(
+ "arduino_board",
+ choices=list(BOARD_PROPERTIES),
+ help="Name of the Arduino board to build for",
+ ),
+ server.ProjectOption("arduino_cli_cmd", help="Path to the arduino-cli tool."),
+ server.ProjectOption("port", help="Port to use for connecting to hardware"),
+ server.ProjectOption(
+ "example_project",
+ help="Type of project to generate.",
+ choices=tuple(PROJECT_TYPES),
+ ),
+ server.ProjectOption(
+ "verbose", help="True to pass --verbose flag to arduino-cli compile and upload"
+ ),
+]
+
+
+class Handler(server.ProjectAPIHandler):
+ def __init__(self):
+ super(Handler, self).__init__()
+ self._proc = None
+ self._port = None
+ self._serial = None
+
+ def server_info_query(self, tvm_version):
+ return server.ServerInfo(
+ platform_name="arduino",
+ is_template=IS_TEMPLATE,
+ model_library_format_path=MODEL_LIBRARY_FORMAT_PATH,
+ project_options=PROJECT_OPTIONS,
+ )
+
+ def _copy_project_files(self, api_server_dir, project_dir, project_type):
+ project_types_folder = api_server_dir.parents[0]
+ shutil.copytree(
+ project_types_folder / project_type / "src", project_dir / "src", dirs_exist_ok=True
+ )
+ # Arduino requires the .ino file have the same filename as its containing folder
+ shutil.copy2(
+ project_types_folder / project_type / "project.ino",
+ project_dir / f"{project_dir.stem}.ino",
+ )
+
+ CRT_COPY_ITEMS = ("include", "src")
+
+ def _copy_standalone_crt(self, source_dir, standalone_crt_dir):
+ # Copy over the standalone_crt directory
+ output_crt_dir = source_dir / "standalone_crt"
+ for item in self.CRT_COPY_ITEMS:
+ src_path = os.path.join(standalone_crt_dir, item)
+ dst_path = output_crt_dir / item
+ if os.path.isdir(src_path):
+ shutil.copytree(src_path, dst_path)
+ else:
+ shutil.copy2(src_path, dst_path)
+
+ # Example project is the "minimum viable project",
+ # and doesn't need a fancy RPC server
+ EXAMPLE_PROJECT_UNUSED_COMPONENTS = [
+ "include/dmlc",
+ "src/support",
+ "src/runtime/minrpc",
+ "src/runtime/crt/graph_executor",
+ "src/runtime/crt/microtvm_rpc_common",
+ "src/runtime/crt/microtvm_rpc_server",
+ "src/runtime/crt/tab",
+ ]
+
+ def _remove_unused_components(self, source_dir, project_type):
+ unused_components = []
+ if project_type == "example_project":
+ unused_components = self.EXAMPLE_PROJECT_UNUSED_COMPONENTS
+
+ for component in unused_components:
+ shutil.rmtree(source_dir / "standalone_crt" / component)
+
+ def _disassemble_mlf(self, mlf_tar_path, source_dir):
+ with tempfile.TemporaryDirectory() as mlf_unpacking_dir_str:
+ mlf_unpacking_dir = pathlib.Path(mlf_unpacking_dir_str)
+ with tarfile.open(mlf_tar_path, "r:") as tar:
+ tar.extractall(mlf_unpacking_dir)
+
+ model_dir = source_dir / "model"
+ model_dir.mkdir()
+
+ # Copy C files from model. The filesnames and quantity
+ # depend on the target string, so we just copy all c files
+ source_dir = mlf_unpacking_dir / "codegen" / "host" / "src"
+ for file in source_dir.rglob(f"*.c"):
+ shutil.copy(file, model_dir)
+
+ # Return metadata.json for use in templating
+ with open(os.path.join(mlf_unpacking_dir, "metadata.json")) as f:
+ metadata = json.load(f)
+ return metadata
+
+ def _template_model_header(self, source_dir, metadata):
+ with open(source_dir / "model.h", "r") as f:
+ model_h_template = Template(f.read())
+
+ template_values = {
+ "workspace_size_bytes": metadata["memory"]["functions"]["main"][0][
+ "workspace_size_bytes"
+ ],
+ }
+
+ with open(source_dir / "model.h", "w") as f:
+ f.write(model_h_template.substitute(template_values))
+
+ # Arduino ONLY recognizes .ino, .ccp, .c, .h
+
+ CPP_FILE_EXTENSION_SYNONYMS = ("cc", "cxx")
+
+ def _change_cpp_file_extensions(self, source_dir):
+ for ext in self.CPP_FILE_EXTENSION_SYNONYMS:
+ for filename in source_dir.rglob(f"*.{ext}"):
+ filename.rename(filename.with_suffix(".cpp"))
+
+ for filename in source_dir.rglob(f"*.inc"):
+ filename.rename(filename.with_suffix(".h"))
+
+ def _process_autogenerated_inc_files(self, source_dir):
+ for filename in source_dir.rglob(f"*.inc"):
+ # Individual file fixes
+ if filename.stem == "gentab_ccitt":
+ with open(filename, "r+") as f:
+ content = f.read()
+ f.seek(0, 0)
+ f.write('#include "inttypes.h"\n' + content)
+
+ filename.rename(filename.with_suffix(".c"))
+
+ POSSIBLE_BASE_PATHS = ["src/standalone_crt/include/", "src/standalone_crt/crt_config/"]
+
+ def _find_modified_include_path(self, project_dir, file_path, import_path):
+ # If the import is for a .inc file we renamed to .c earlier, fix it
+ if import_path.endswith(self.CPP_FILE_EXTENSION_SYNONYMS):
+ import_path = re.sub(r"\.[a-z]+$", ".cpp", import_path)
+
+ if import_path.endswith(".inc"):
+ import_path = re.sub(r"\.[a-z]+$", ".h", import_path)
+
+ # If the import already works, don't modify it
+ if (file_path.parents[0] / import_path).exists():
+ return import_path
+
+ relative_path = file_path.relative_to(project_dir)
+ up_dirs_path = "../" * str(relative_path).count("/")
+
+ for base_path in self.POSSIBLE_BASE_PATHS:
+ full_potential_path = project_dir / base_path / import_path
+ if full_potential_path.exists():
+ new_include = up_dirs_path + base_path + import_path
+ return new_include
+
+ # If we can't find the file, just leave it untouched
+ # It's probably a standard C/C++ header
+ return import_path
+
+ # Arduino only supports imports relative to the top-level project,
+ # so we need to adjust each import to meet this convention
+ def _convert_imports(self, project_dir, source_dir):
+ for ext in ("c", "h", "cpp"):
+ for filename in source_dir.rglob(f"*.{ext}"):
+ with filename.open() as file:
+ lines = file.readlines()
+
+ for i in range(len(lines)):
+ # Check if line has an include
+ result = re.search(r"#include\s*[<\"]([^>]*)[>\"]", lines[i])
+ if not result:
+ continue
+ new_include = self._find_modified_include_path(
+ project_dir, filename, result.groups()[0]
+ )
+
+ lines[i] = f'#include "{new_include}"\n'
+
+ with filename.open("w") as file:
+ file.writelines(lines)
+
+ def generate_project(self, model_library_format_path, standalone_crt_dir, project_dir, options):
+ # Reference key directories with pathlib
+ project_dir = pathlib.Path(project_dir)
+ project_dir.mkdir()
+ source_dir = project_dir / "src"
+ source_dir.mkdir()
+
+ # Copies files from the template folder to project_dir. model.h is copied here,
+ # but will also need to be templated later.
+ if IS_TEMPLATE:
+ shutil.copy2(API_SERVER_DIR / "microtvm_api_server.py", project_dir)
+ self._copy_project_files(API_SERVER_DIR, project_dir, options["project_type"])
+
+ # Copy standalone_crt into src folder
+ self._copy_standalone_crt(source_dir, standalone_crt_dir)
+ self._remove_unused_components(source_dir, options["project_type"])
+
+ # Unpack the MLF and copy the relevant files
+ metadata = self._disassemble_mlf(model_library_format_path, source_dir)
+ shutil.copy2(model_library_format_path, source_dir / "model")
+
+ # For AOT, template model.h with metadata to minimize space usage
+ if options["project_type"] == "example_project":
+ self._template_model_header(source_dir, metadata)
+
+ self._change_cpp_file_extensions(source_dir)
+
+ # Recursively change imports
+ self._convert_imports(project_dir, source_dir)
+
+ def _get_fqbn(self, options):
+ o = BOARD_PROPERTIES[options["arduino_board"]]
+ return f"{o['package']}:{o['architecture']}:{o['board']}"
+
+ def build(self, options):
+ BUILD_DIR.mkdir()
+ print(BUILD_DIR)
+
+ compile_cmd = [
+ options["arduino_cli_cmd"],
+ "compile",
+ "./project/",
+ "--fqbn",
+ self._get_fqbn(options),
+ "--build-path",
+ BUILD_DIR.resolve(),
+ ]
+
+ if options.get("verbose"):
+ compile_cmd.append("--verbose")
+
+ # Specify project to compile
+ output = subprocess.check_call(compile_cmd)
+ assert output == 0
+
+ # We run the command `arduino-cli board list`, which produces
+ # outputs of the form:
+ """
+ Port Type Board Name FQBN Core
+ /dev/ttyS4 Serial Port Unknown
+ /dev/ttyUSB0 Serial Port (USB) Spresense SPRESENSE:spresense:spresense SPRESENSE:spresense
+ """
+
+ def _auto_detect_port(self, options):
+ list_cmd = [options["arduino_cli_cmd"], "board", "list"]
+ list_cmd_output = subprocess.check_output(list_cmd).decode("utf-8")
+ # Remove header and new lines at bottom
+ port_options = list_cmd_output.split("\n")[1:-2]
+
+ # Select the first compatible board
+ fqbn = self._get_fqbn(options)
+ for port_option in port_options:
+ if fqbn in port_option:
Review comment:
(if this is too complicated, add a TODO instead)
can you determine the exact part of each line to look at by doing
```
fqbn_index = list_cmd_output.split("\n", 1)[0].index("FQBN")
core_index = list_cmd_output.split("\n", 1)[0].index("Core")
# ...
port_fqbn = port_option[fqbn_index:core_index].rstrip()
if fqbn == port_fqbn:
return port_option.split(" ")[0]
```
##########
File path: apps/microtvm/arduino/template_project/microtvm_api_server.py
##########
@@ -0,0 +1,423 @@
+import collections
+import functools
+import json
+import logging
+import os
+import os.path
+import pathlib
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tarfile
+from string import Template
+import tempfile
+import time
+
+import serial
+import serial.tools.list_ports
+
+from tvm.micro.project_api import server
+
+MODEL_LIBRARY_FORMAT_RELPATH = pathlib.Path("src") / "model" / "model.tar"
+API_SERVER_DIR = pathlib.Path(os.path.dirname(__file__) or os.path.getcwd())
+BUILD_DIR = API_SERVER_DIR / "build"
+MODEL_LIBRARY_FORMAT_PATH = API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH
+
+IS_TEMPLATE = not (API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH).exists()
+
+
+class InvalidPortException(Exception):
+ """Raised when the given port could not be opened"""
+
+
+class SketchUploadException(Exception):
+ """Raised when a sketch cannot be uploaded for an unknown reason."""
+
+
+class BoardAutodetectFailed(Exception):
+ """Raised when no attached hardware is found matching the requested board"""
+
+
+BOARD_PROPERTIES = {
+ "due": {"package": "arduino", "architecture": "sam", "board": "arduino_due_x"},
+ # Due to the way the Feather S2 bootloader works, compilation
+ # behaves fine but uploads cannot be done automatically
+ "feathers2": {
+ "package": "esp32",
+ "architecture": "esp32",
+ "board": "feathers2",
+ },
+ # Spresense only works as of its v2.3.0 sdk
+ "spresense": {
+ "package": "SPRESENSE",
+ "architecture": "spresense",
+ "board": "spresense",
+ },
+ "nano33ble": {
+ "package": "arduino",
+ "architecture": "mbed_nano",
+ "board": "nano33ble",
+ },
+ "pybadge": {
+ "package": "adafruit",
+ "architecture": "samd",
+ "board": "adafruit_pybadge_m4",
+ },
+ # The Teensy boards are listed here for completeness, but they
+ # won't work until https://github.com/arduino/arduino-cli/issues/700
+ # is finished
+ "teensy40": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy40",
+ },
+ "teensy41": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy41",
+ },
+}
+
+PROJECT_TYPES = ["example_project", "host_driven"]
+
+PROJECT_OPTIONS = [
+ server.ProjectOption(
+ "arduino_board",
+ choices=list(BOARD_PROPERTIES),
+ help="Name of the Arduino board to build for",
+ ),
+ server.ProjectOption("arduino_cli_cmd", help="Path to the arduino-cli tool."),
+ server.ProjectOption("port", help="Port to use for connecting to hardware"),
+ server.ProjectOption(
+ "example_project",
+ help="Type of project to generate.",
+ choices=tuple(PROJECT_TYPES),
+ ),
+ server.ProjectOption(
+ "verbose", help="True to pass --verbose flag to arduino-cli compile and upload"
+ ),
+]
+
+
+class Handler(server.ProjectAPIHandler):
+ def __init__(self):
+ super(Handler, self).__init__()
+ self._proc = None
+ self._port = None
+ self._serial = None
+
+ def server_info_query(self, tvm_version):
+ return server.ServerInfo(
+ platform_name="arduino",
+ is_template=IS_TEMPLATE,
+ model_library_format_path=MODEL_LIBRARY_FORMAT_PATH,
+ project_options=PROJECT_OPTIONS,
+ )
+
+ def _copy_project_files(self, api_server_dir, project_dir, project_type):
+ project_types_folder = api_server_dir.parents[0]
+ shutil.copytree(
+ project_types_folder / project_type / "src", project_dir / "src", dirs_exist_ok=True
+ )
+ # Arduino requires the .ino file have the same filename as its containing folder
+ shutil.copy2(
+ project_types_folder / project_type / "project.ino",
+ project_dir / f"{project_dir.stem}.ino",
+ )
+
+ CRT_COPY_ITEMS = ("include", "src")
+
+ def _copy_standalone_crt(self, source_dir, standalone_crt_dir):
+ # Copy over the standalone_crt directory
+ output_crt_dir = source_dir / "standalone_crt"
+ for item in self.CRT_COPY_ITEMS:
+ src_path = os.path.join(standalone_crt_dir, item)
+ dst_path = output_crt_dir / item
+ if os.path.isdir(src_path):
+ shutil.copytree(src_path, dst_path)
+ else:
+ shutil.copy2(src_path, dst_path)
+
+ # Example project is the "minimum viable project",
+ # and doesn't need a fancy RPC server
+ EXAMPLE_PROJECT_UNUSED_COMPONENTS = [
+ "include/dmlc",
+ "src/support",
+ "src/runtime/minrpc",
+ "src/runtime/crt/graph_executor",
+ "src/runtime/crt/microtvm_rpc_common",
+ "src/runtime/crt/microtvm_rpc_server",
+ "src/runtime/crt/tab",
+ ]
+
+ def _remove_unused_components(self, source_dir, project_type):
+ unused_components = []
+ if project_type == "example_project":
+ unused_components = self.EXAMPLE_PROJECT_UNUSED_COMPONENTS
+
+ for component in unused_components:
+ shutil.rmtree(source_dir / "standalone_crt" / component)
+
+ def _disassemble_mlf(self, mlf_tar_path, source_dir):
+ with tempfile.TemporaryDirectory() as mlf_unpacking_dir_str:
+ mlf_unpacking_dir = pathlib.Path(mlf_unpacking_dir_str)
+ with tarfile.open(mlf_tar_path, "r:") as tar:
+ tar.extractall(mlf_unpacking_dir)
+
+ model_dir = source_dir / "model"
+ model_dir.mkdir()
+
+ # Copy C files from model. The filesnames and quantity
+ # depend on the target string, so we just copy all c files
+ source_dir = mlf_unpacking_dir / "codegen" / "host" / "src"
+ for file in source_dir.rglob(f"*.c"):
+ shutil.copy(file, model_dir)
+
+ # Return metadata.json for use in templating
+ with open(os.path.join(mlf_unpacking_dir, "metadata.json")) as f:
+ metadata = json.load(f)
+ return metadata
+
+ def _template_model_header(self, source_dir, metadata):
+ with open(source_dir / "model.h", "r") as f:
+ model_h_template = Template(f.read())
+
+ template_values = {
+ "workspace_size_bytes": metadata["memory"]["functions"]["main"][0][
+ "workspace_size_bytes"
+ ],
+ }
+
+ with open(source_dir / "model.h", "w") as f:
+ f.write(model_h_template.substitute(template_values))
+
+ # Arduino ONLY recognizes .ino, .ccp, .c, .h
+
+ CPP_FILE_EXTENSION_SYNONYMS = ("cc", "cxx")
+
+ def _change_cpp_file_extensions(self, source_dir):
+ for ext in self.CPP_FILE_EXTENSION_SYNONYMS:
+ for filename in source_dir.rglob(f"*.{ext}"):
+ filename.rename(filename.with_suffix(".cpp"))
+
+ for filename in source_dir.rglob(f"*.inc"):
+ filename.rename(filename.with_suffix(".h"))
+
+ def _process_autogenerated_inc_files(self, source_dir):
+ for filename in source_dir.rglob(f"*.inc"):
+ # Individual file fixes
+ if filename.stem == "gentab_ccitt":
+ with open(filename, "r+") as f:
+ content = f.read()
+ f.seek(0, 0)
+ f.write('#include "inttypes.h"\n' + content)
+
+ filename.rename(filename.with_suffix(".c"))
+
+ POSSIBLE_BASE_PATHS = ["src/standalone_crt/include/", "src/standalone_crt/crt_config/"]
+
+ def _find_modified_include_path(self, project_dir, file_path, import_path):
+ # If the import is for a .inc file we renamed to .c earlier, fix it
+ if import_path.endswith(self.CPP_FILE_EXTENSION_SYNONYMS):
+ import_path = re.sub(r"\.[a-z]+$", ".cpp", import_path)
+
+ if import_path.endswith(".inc"):
+ import_path = re.sub(r"\.[a-z]+$", ".h", import_path)
+
+ # If the import already works, don't modify it
+ if (file_path.parents[0] / import_path).exists():
+ return import_path
+
+ relative_path = file_path.relative_to(project_dir)
+ up_dirs_path = "../" * str(relative_path).count("/")
+
+ for base_path in self.POSSIBLE_BASE_PATHS:
+ full_potential_path = project_dir / base_path / import_path
+ if full_potential_path.exists():
+ new_include = up_dirs_path + base_path + import_path
+ return new_include
+
+ # If we can't find the file, just leave it untouched
+ # It's probably a standard C/C++ header
+ return import_path
+
+ # Arduino only supports imports relative to the top-level project,
+ # so we need to adjust each import to meet this convention
+ def _convert_imports(self, project_dir, source_dir):
+ for ext in ("c", "h", "cpp"):
+ for filename in source_dir.rglob(f"*.{ext}"):
+ with filename.open() as file:
+ lines = file.readlines()
+
+ for i in range(len(lines)):
+ # Check if line has an include
+ result = re.search(r"#include\s*[<\"]([^>]*)[>\"]", lines[i])
+ if not result:
+ continue
+ new_include = self._find_modified_include_path(
+ project_dir, filename, result.groups()[0]
+ )
+
+ lines[i] = f'#include "{new_include}"\n'
+
+ with filename.open("w") as file:
+ file.writelines(lines)
+
+ def generate_project(self, model_library_format_path, standalone_crt_dir, project_dir, options):
+ # Reference key directories with pathlib
+ project_dir = pathlib.Path(project_dir)
+ project_dir.mkdir()
+ source_dir = project_dir / "src"
+ source_dir.mkdir()
+
+ # Copies files from the template folder to project_dir. model.h is copied here,
+ # but will also need to be templated later.
+ if IS_TEMPLATE:
+ shutil.copy2(API_SERVER_DIR / "microtvm_api_server.py", project_dir)
+ self._copy_project_files(API_SERVER_DIR, project_dir, options["project_type"])
+
+ # Copy standalone_crt into src folder
+ self._copy_standalone_crt(source_dir, standalone_crt_dir)
+ self._remove_unused_components(source_dir, options["project_type"])
+
+ # Unpack the MLF and copy the relevant files
+ metadata = self._disassemble_mlf(model_library_format_path, source_dir)
+ shutil.copy2(model_library_format_path, source_dir / "model")
+
+ # For AOT, template model.h with metadata to minimize space usage
+ if options["project_type"] == "example_project":
+ self._template_model_header(source_dir, metadata)
+
+ self._change_cpp_file_extensions(source_dir)
+
+ # Recursively change imports
+ self._convert_imports(project_dir, source_dir)
+
+ def _get_fqbn(self, options):
+ o = BOARD_PROPERTIES[options["arduino_board"]]
+ return f"{o['package']}:{o['architecture']}:{o['board']}"
+
+ def build(self, options):
+ BUILD_DIR.mkdir()
+ print(BUILD_DIR)
+
+ compile_cmd = [
+ options["arduino_cli_cmd"],
+ "compile",
+ "./project/",
+ "--fqbn",
+ self._get_fqbn(options),
+ "--build-path",
+ BUILD_DIR.resolve(),
+ ]
+
+ if options.get("verbose"):
+ compile_cmd.append("--verbose")
+
+ # Specify project to compile
+ output = subprocess.check_call(compile_cmd)
+ assert output == 0
+
+ # We run the command `arduino-cli board list`, which produces
+ # outputs of the form:
+ """
+ Port Type Board Name FQBN Core
+ /dev/ttyS4 Serial Port Unknown
+ /dev/ttyUSB0 Serial Port (USB) Spresense SPRESENSE:spresense:spresense SPRESENSE:spresense
+ """
+
+ def _auto_detect_port(self, options):
+ list_cmd = [options["arduino_cli_cmd"], "board", "list"]
+ list_cmd_output = subprocess.check_output(list_cmd).decode("utf-8")
+ # Remove header and new lines at bottom
+ port_options = list_cmd_output.split("\n")[1:-2]
+
+ # Select the first compatible board
+ fqbn = self._get_fqbn(options)
+ for port_option in port_options:
+ if fqbn in port_option:
+ return port_option.split(" ")[0]
+
+ # If no compatible boards, raise an error
+ raise BoardAutodetectFailed()
+
+ def _get_arduino_port(self, options):
+ if not self._port:
+ if "port" in options and options["port"]:
+ self._port = options["port"]
+ else:
+ self._port = self._auto_detect_port(options)
+
+ return self._port
+
+ def flash(self, options):
+ port = self._get_arduino_port(options)
+
+ upload_cmd = [
+ options["arduino_cli_cmd"],
+ "upload",
+ "./project",
+ "--fqbn",
+ self._get_fqbn(options),
+ "--input-dir",
+ BUILD_DIR.resolve(),
+ "--port",
+ port,
+ ]
+
+ if options.get("verbose"):
+ upload_cmd.append("--verbose")
+
+ output = subprocess.check_call(upload_cmd)
+
+ if output == 2:
+ raise InvalidPortException()
+ elif output > 0:
+ raise SketchUploadException()
+
+ def open_transport(self, options):
+ # Zephyr example doesn't throw an error in this case
+ if self._serial is not None:
+ return
+
+ port = self._get_arduino_port(options)
+
+ # Wait for port to become available
+ for attempts in range(10):
+ if any(serial.tools.list_ports.grep(port)):
+ break
+ time.sleep(0.5)
+
+ # TODO figure out why RPC serial communication times out 90% (not 100%)
Review comment:
can you include stacktrace as a GH issue and link here?
##########
File path: tests/micro/zephyr/conftest.py
##########
@@ -89,6 +89,8 @@ def temp_dir(platform):
_, zephyr_board = PLATFORMS[platform]
parent_dir = pathlib.Path(os.path.dirname(__file__))
filename = os.path.splitext(os.path.basename(__file__))[0]
+ print(filename)
+ print("-----------------")
Review comment:
nit: revert
##########
File path: tests/micro/arduino/test_arduino_rpc_server.py
##########
@@ -0,0 +1,352 @@
+import datetime
+import os
+import pathlib
+import shutil
+import sys
+import time
+
+import numpy as np
+import onnx
+from PIL import Image
+import pytest
+import tflite
+
+import tvm
+from tvm import micro, relay
+from tvm.relay.testing import byoc
+
+import conftest
+
+"""
+This unit test simulates an autotuning workflow, where we:
+1. Instantiate the Arduino RPC server project
+2. Build and flash that project onto our target board
+
+"""
+
+# We'll make a new workspace for each test
+@pytest.fixture(scope="function")
+def workspace_dir(platform, run_hardware_tests):
+ if not run_hardware_tests:
+ pytest.skip()
+
+ return conftest.make_workspace_dir("arduino_rpc_server", platform)
+
+
+def _make_session(model, arduino_board, arduino_cli_cmd, workspace_dir, mod, build_config):
+ project = tvm.micro.generate_project(
+ str(conftest.TEMPLATE_PROJECT_DIR),
+ mod,
+ workspace_dir / "project",
+ {
+ "arduino_board": arduino_board,
+ "arduino_cli_cmd": arduino_cli_cmd,
+ "project_type": "host_driven",
+ "verbose": bool(build_config.get("debug")),
+ },
+ )
+ project.build()
+ project.flash()
+ return tvm.micro.Session(project.transport())
+
+
+def _make_sess_from_op(
+ model, arduino_board, arduino_cli_cmd, workspace_dir, op_name, sched, arg_bufs, build_config
+):
+ target = tvm.target.target.micro(model)
+ with tvm.transform.PassContext(opt_level=3, config={"tir.disable_vectorize": True}):
+ mod = tvm.build(sched, arg_bufs, target=target, name=op_name)
+
+ return _make_session(model, arduino_board, arduino_cli_cmd, workspace_dir, mod, build_config)
+
+
+def _make_add_sess(model, arduino_board, arduino_cli_cmd, workspace_dir, build_config):
+ A = tvm.te.placeholder((2,), dtype="int8")
+ B = tvm.te.placeholder((1,), dtype="int8")
+ C = tvm.te.compute(A.shape, lambda i: A[i] + B[0], name="C")
+ sched = tvm.te.create_schedule(C.op)
+ return _make_sess_from_op(
+ model, arduino_board, arduino_cli_cmd, workspace_dir, "add", sched, [A, B, C], build_config
+ )
+
+
+# The same test code can be executed on both the QEMU simulation and on real hardware.
+@tvm.testing.requires_micro
+def test_compile_runtime(platform, arduino_cli_cmd, tvm_debug, workspace_dir):
+ """Test compiling the on-device runtime."""
+
+ model, arduino_board = conftest.PLATFORMS[platform]
+ build_config = {"debug": tvm_debug}
+
+ # NOTE: run test in a nested function so cPython will delete arrays before closing the session.
+ def test_basic_add(sess):
+ A_data = tvm.nd.array(np.array([2, 3], dtype="int8"), device=sess.device)
+ assert (A_data.numpy() == np.array([2, 3])).all()
+ B_data = tvm.nd.array(np.array([4], dtype="int8"), device=sess.device)
+ assert (B_data.numpy() == np.array([4])).all()
+ C_data = tvm.nd.array(np.array([0, 0], dtype="int8"), device=sess.device)
+ assert (C_data.numpy() == np.array([0, 0])).all()
+
+ system_lib = sess.get_system_lib()
+ system_lib.get_function("add")(A_data, B_data, C_data)
+ assert (C_data.numpy() == np.array([6, 7])).all()
+
+ with _make_add_sess(model, arduino_board, arduino_cli_cmd, workspace_dir, build_config) as sess:
+ test_basic_add(sess)
+ print(workspace_dir)
Review comment:
nit: rm
##########
File path: apps/microtvm/arduino/host_driven/src/model.c
##########
@@ -0,0 +1,73 @@
+#ifndef TVM_IMPLEMENTATION_ARDUINO
Review comment:
what's this for?
##########
File path: apps/microtvm/arduino/host_driven/project.ino
##########
@@ -0,0 +1,34 @@
+#include "src/standalone_crt/include/tvm/runtime/crt/microtvm_rpc_server.h"
+#include "src/standalone_crt/include/tvm/runtime/crt/logging.h"
+#include "src/model.h"
+microtvm_rpc_server_t server;
+
+// Called by TVM to write serial data to the UART.
+ssize_t write_serial(void* unused_context, const uint8_t* data, size_t size) {
+ Serial.write(data, size);
+ return size;
+}
+
+void setup() {
+ server = MicroTVMRpcServerInit(write_serial, NULL);
+ TVMLogf("microTVM Arduino runtime - running");
+ Serial.begin(115200);
+}
+
+void loop() {
+ int to_read = Serial.available();
Review comment:
i would suggest clipping this to a max value of e.g. 128. otherwise, you risk stack blowup
##########
File path: apps/microtvm/arduino/host_driven/src/model.c
##########
@@ -0,0 +1,73 @@
+#ifndef TVM_IMPLEMENTATION_ARDUINO
+#define TVM_IMPLEMENTATION_ARDUINO
+
+#include "model.h"
Review comment:
wondering if we should choose model.h or a different e.g. model_support.h? nothing in here is the model itself--just helper functions.
##########
File path: apps/microtvm/arduino/template_project/microtvm_api_server.py
##########
@@ -0,0 +1,423 @@
+import collections
+import functools
+import json
+import logging
+import os
+import os.path
+import pathlib
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tarfile
+from string import Template
+import tempfile
+import time
+
+import serial
+import serial.tools.list_ports
+
+from tvm.micro.project_api import server
+
+MODEL_LIBRARY_FORMAT_RELPATH = pathlib.Path("src") / "model" / "model.tar"
+API_SERVER_DIR = pathlib.Path(os.path.dirname(__file__) or os.path.getcwd())
+BUILD_DIR = API_SERVER_DIR / "build"
+MODEL_LIBRARY_FORMAT_PATH = API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH
+
+IS_TEMPLATE = not (API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH).exists()
+
+
+class InvalidPortException(Exception):
+ """Raised when the given port could not be opened"""
+
+
+class SketchUploadException(Exception):
+ """Raised when a sketch cannot be uploaded for an unknown reason."""
+
+
+class BoardAutodetectFailed(Exception):
+ """Raised when no attached hardware is found matching the requested board"""
+
+
+BOARD_PROPERTIES = {
+ "due": {"package": "arduino", "architecture": "sam", "board": "arduino_due_x"},
+ # Due to the way the Feather S2 bootloader works, compilation
+ # behaves fine but uploads cannot be done automatically
+ "feathers2": {
+ "package": "esp32",
+ "architecture": "esp32",
+ "board": "feathers2",
+ },
+ # Spresense only works as of its v2.3.0 sdk
+ "spresense": {
+ "package": "SPRESENSE",
+ "architecture": "spresense",
+ "board": "spresense",
+ },
+ "nano33ble": {
+ "package": "arduino",
+ "architecture": "mbed_nano",
+ "board": "nano33ble",
+ },
+ "pybadge": {
+ "package": "adafruit",
+ "architecture": "samd",
+ "board": "adafruit_pybadge_m4",
+ },
+ # The Teensy boards are listed here for completeness, but they
+ # won't work until https://github.com/arduino/arduino-cli/issues/700
+ # is finished
+ "teensy40": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy40",
+ },
+ "teensy41": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy41",
+ },
+}
+
+PROJECT_TYPES = ["example_project", "host_driven"]
+
+PROJECT_OPTIONS = [
+ server.ProjectOption(
+ "arduino_board",
+ choices=list(BOARD_PROPERTIES),
+ help="Name of the Arduino board to build for",
+ ),
+ server.ProjectOption("arduino_cli_cmd", help="Path to the arduino-cli tool."),
+ server.ProjectOption("port", help="Port to use for connecting to hardware"),
+ server.ProjectOption(
+ "example_project",
+ help="Type of project to generate.",
+ choices=tuple(PROJECT_TYPES),
+ ),
+ server.ProjectOption(
+ "verbose", help="True to pass --verbose flag to arduino-cli compile and upload"
+ ),
+]
+
+
+class Handler(server.ProjectAPIHandler):
+ def __init__(self):
+ super(Handler, self).__init__()
+ self._proc = None
+ self._port = None
+ self._serial = None
+
+ def server_info_query(self, tvm_version):
+ return server.ServerInfo(
+ platform_name="arduino",
+ is_template=IS_TEMPLATE,
+ model_library_format_path=MODEL_LIBRARY_FORMAT_PATH,
+ project_options=PROJECT_OPTIONS,
+ )
+
+ def _copy_project_files(self, api_server_dir, project_dir, project_type):
+ project_types_folder = api_server_dir.parents[0]
+ shutil.copytree(
+ project_types_folder / project_type / "src", project_dir / "src", dirs_exist_ok=True
+ )
+ # Arduino requires the .ino file have the same filename as its containing folder
+ shutil.copy2(
+ project_types_folder / project_type / "project.ino",
+ project_dir / f"{project_dir.stem}.ino",
+ )
+
+ CRT_COPY_ITEMS = ("include", "src")
+
+ def _copy_standalone_crt(self, source_dir, standalone_crt_dir):
+ # Copy over the standalone_crt directory
+ output_crt_dir = source_dir / "standalone_crt"
+ for item in self.CRT_COPY_ITEMS:
+ src_path = os.path.join(standalone_crt_dir, item)
+ dst_path = output_crt_dir / item
+ if os.path.isdir(src_path):
+ shutil.copytree(src_path, dst_path)
+ else:
+ shutil.copy2(src_path, dst_path)
+
+ # Example project is the "minimum viable project",
+ # and doesn't need a fancy RPC server
+ EXAMPLE_PROJECT_UNUSED_COMPONENTS = [
+ "include/dmlc",
+ "src/support",
+ "src/runtime/minrpc",
+ "src/runtime/crt/graph_executor",
+ "src/runtime/crt/microtvm_rpc_common",
+ "src/runtime/crt/microtvm_rpc_server",
+ "src/runtime/crt/tab",
+ ]
+
+ def _remove_unused_components(self, source_dir, project_type):
+ unused_components = []
+ if project_type == "example_project":
+ unused_components = self.EXAMPLE_PROJECT_UNUSED_COMPONENTS
+
+ for component in unused_components:
+ shutil.rmtree(source_dir / "standalone_crt" / component)
+
+ def _disassemble_mlf(self, mlf_tar_path, source_dir):
+ with tempfile.TemporaryDirectory() as mlf_unpacking_dir_str:
+ mlf_unpacking_dir = pathlib.Path(mlf_unpacking_dir_str)
+ with tarfile.open(mlf_tar_path, "r:") as tar:
+ tar.extractall(mlf_unpacking_dir)
+
+ model_dir = source_dir / "model"
+ model_dir.mkdir()
+
+ # Copy C files from model. The filesnames and quantity
+ # depend on the target string, so we just copy all c files
+ source_dir = mlf_unpacking_dir / "codegen" / "host" / "src"
+ for file in source_dir.rglob(f"*.c"):
+ shutil.copy(file, model_dir)
+
+ # Return metadata.json for use in templating
+ with open(os.path.join(mlf_unpacking_dir, "metadata.json")) as f:
+ metadata = json.load(f)
+ return metadata
+
+ def _template_model_header(self, source_dir, metadata):
+ with open(source_dir / "model.h", "r") as f:
+ model_h_template = Template(f.read())
+
+ template_values = {
+ "workspace_size_bytes": metadata["memory"]["functions"]["main"][0][
+ "workspace_size_bytes"
+ ],
+ }
+
+ with open(source_dir / "model.h", "w") as f:
+ f.write(model_h_template.substitute(template_values))
+
+ # Arduino ONLY recognizes .ino, .ccp, .c, .h
+
+ CPP_FILE_EXTENSION_SYNONYMS = ("cc", "cxx")
+
+ def _change_cpp_file_extensions(self, source_dir):
+ for ext in self.CPP_FILE_EXTENSION_SYNONYMS:
+ for filename in source_dir.rglob(f"*.{ext}"):
+ filename.rename(filename.with_suffix(".cpp"))
+
+ for filename in source_dir.rglob(f"*.inc"):
+ filename.rename(filename.with_suffix(".h"))
+
+ def _process_autogenerated_inc_files(self, source_dir):
+ for filename in source_dir.rglob(f"*.inc"):
+ # Individual file fixes
+ if filename.stem == "gentab_ccitt":
+ with open(filename, "r+") as f:
+ content = f.read()
+ f.seek(0, 0)
+ f.write('#include "inttypes.h"\n' + content)
Review comment:
comment somewhere in here to explain why you need to do this
##########
File path: apps/microtvm/arduino/host_driven/project.ino
##########
@@ -0,0 +1,34 @@
+#include "src/standalone_crt/include/tvm/runtime/crt/microtvm_rpc_server.h"
+#include "src/standalone_crt/include/tvm/runtime/crt/logging.h"
+#include "src/model.h"
+microtvm_rpc_server_t server;
+
+// Called by TVM to write serial data to the UART.
+ssize_t write_serial(void* unused_context, const uint8_t* data, size_t size) {
+ Serial.write(data, size);
+ return size;
+}
+
+void setup() {
+ server = MicroTVMRpcServerInit(write_serial, NULL);
+ TVMLogf("microTVM Arduino runtime - running");
+ Serial.begin(115200);
Review comment:
can you seed the random number generator here, if needed?
##########
File path: apps/microtvm/arduino/template_project/microtvm_api_server.py
##########
@@ -0,0 +1,423 @@
+import collections
+import functools
+import json
+import logging
+import os
+import os.path
+import pathlib
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tarfile
+from string import Template
+import tempfile
+import time
+
+import serial
+import serial.tools.list_ports
+
+from tvm.micro.project_api import server
+
+MODEL_LIBRARY_FORMAT_RELPATH = pathlib.Path("src") / "model" / "model.tar"
+API_SERVER_DIR = pathlib.Path(os.path.dirname(__file__) or os.path.getcwd())
+BUILD_DIR = API_SERVER_DIR / "build"
+MODEL_LIBRARY_FORMAT_PATH = API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH
+
+IS_TEMPLATE = not (API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH).exists()
+
+
+class InvalidPortException(Exception):
+ """Raised when the given port could not be opened"""
+
+
+class SketchUploadException(Exception):
+ """Raised when a sketch cannot be uploaded for an unknown reason."""
+
+
+class BoardAutodetectFailed(Exception):
+ """Raised when no attached hardware is found matching the requested board"""
+
+
+BOARD_PROPERTIES = {
+ "due": {"package": "arduino", "architecture": "sam", "board": "arduino_due_x"},
+ # Due to the way the Feather S2 bootloader works, compilation
+ # behaves fine but uploads cannot be done automatically
+ "feathers2": {
+ "package": "esp32",
+ "architecture": "esp32",
+ "board": "feathers2",
+ },
+ # Spresense only works as of its v2.3.0 sdk
+ "spresense": {
+ "package": "SPRESENSE",
+ "architecture": "spresense",
+ "board": "spresense",
+ },
+ "nano33ble": {
+ "package": "arduino",
+ "architecture": "mbed_nano",
+ "board": "nano33ble",
+ },
+ "pybadge": {
+ "package": "adafruit",
+ "architecture": "samd",
+ "board": "adafruit_pybadge_m4",
+ },
+ # The Teensy boards are listed here for completeness, but they
+ # won't work until https://github.com/arduino/arduino-cli/issues/700
+ # is finished
+ "teensy40": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy40",
+ },
+ "teensy41": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy41",
+ },
+}
+
+PROJECT_TYPES = ["example_project", "host_driven"]
+
+PROJECT_OPTIONS = [
+ server.ProjectOption(
+ "arduino_board",
+ choices=list(BOARD_PROPERTIES),
+ help="Name of the Arduino board to build for",
+ ),
+ server.ProjectOption("arduino_cli_cmd", help="Path to the arduino-cli tool."),
+ server.ProjectOption("port", help="Port to use for connecting to hardware"),
+ server.ProjectOption(
+ "example_project",
+ help="Type of project to generate.",
+ choices=tuple(PROJECT_TYPES),
+ ),
+ server.ProjectOption(
+ "verbose", help="True to pass --verbose flag to arduino-cli compile and upload"
+ ),
+]
+
+
+class Handler(server.ProjectAPIHandler):
+ def __init__(self):
+ super(Handler, self).__init__()
+ self._proc = None
+ self._port = None
+ self._serial = None
+
+ def server_info_query(self, tvm_version):
+ return server.ServerInfo(
+ platform_name="arduino",
+ is_template=IS_TEMPLATE,
+ model_library_format_path=MODEL_LIBRARY_FORMAT_PATH,
+ project_options=PROJECT_OPTIONS,
+ )
+
+ def _copy_project_files(self, api_server_dir, project_dir, project_type):
+ project_types_folder = api_server_dir.parents[0]
+ shutil.copytree(
+ project_types_folder / project_type / "src", project_dir / "src", dirs_exist_ok=True
+ )
+ # Arduino requires the .ino file have the same filename as its containing folder
+ shutil.copy2(
+ project_types_folder / project_type / "project.ino",
+ project_dir / f"{project_dir.stem}.ino",
+ )
+
+ CRT_COPY_ITEMS = ("include", "src")
+
+ def _copy_standalone_crt(self, source_dir, standalone_crt_dir):
+ # Copy over the standalone_crt directory
+ output_crt_dir = source_dir / "standalone_crt"
+ for item in self.CRT_COPY_ITEMS:
+ src_path = os.path.join(standalone_crt_dir, item)
+ dst_path = output_crt_dir / item
+ if os.path.isdir(src_path):
+ shutil.copytree(src_path, dst_path)
+ else:
+ shutil.copy2(src_path, dst_path)
+
+ # Example project is the "minimum viable project",
+ # and doesn't need a fancy RPC server
+ EXAMPLE_PROJECT_UNUSED_COMPONENTS = [
+ "include/dmlc",
+ "src/support",
+ "src/runtime/minrpc",
+ "src/runtime/crt/graph_executor",
+ "src/runtime/crt/microtvm_rpc_common",
+ "src/runtime/crt/microtvm_rpc_server",
+ "src/runtime/crt/tab",
+ ]
+
+ def _remove_unused_components(self, source_dir, project_type):
+ unused_components = []
+ if project_type == "example_project":
+ unused_components = self.EXAMPLE_PROJECT_UNUSED_COMPONENTS
+
+ for component in unused_components:
+ shutil.rmtree(source_dir / "standalone_crt" / component)
+
+ def _disassemble_mlf(self, mlf_tar_path, source_dir):
+ with tempfile.TemporaryDirectory() as mlf_unpacking_dir_str:
+ mlf_unpacking_dir = pathlib.Path(mlf_unpacking_dir_str)
+ with tarfile.open(mlf_tar_path, "r:") as tar:
+ tar.extractall(mlf_unpacking_dir)
+
+ model_dir = source_dir / "model"
+ model_dir.mkdir()
+
+ # Copy C files from model. The filesnames and quantity
+ # depend on the target string, so we just copy all c files
+ source_dir = mlf_unpacking_dir / "codegen" / "host" / "src"
+ for file in source_dir.rglob(f"*.c"):
+ shutil.copy(file, model_dir)
+
+ # Return metadata.json for use in templating
+ with open(os.path.join(mlf_unpacking_dir, "metadata.json")) as f:
+ metadata = json.load(f)
+ return metadata
+
+ def _template_model_header(self, source_dir, metadata):
+ with open(source_dir / "model.h", "r") as f:
+ model_h_template = Template(f.read())
+
+ template_values = {
+ "workspace_size_bytes": metadata["memory"]["functions"]["main"][0][
+ "workspace_size_bytes"
+ ],
+ }
+
+ with open(source_dir / "model.h", "w") as f:
+ f.write(model_h_template.substitute(template_values))
+
+ # Arduino ONLY recognizes .ino, .ccp, .c, .h
+
+ CPP_FILE_EXTENSION_SYNONYMS = ("cc", "cxx")
+
+ def _change_cpp_file_extensions(self, source_dir):
+ for ext in self.CPP_FILE_EXTENSION_SYNONYMS:
+ for filename in source_dir.rglob(f"*.{ext}"):
+ filename.rename(filename.with_suffix(".cpp"))
+
+ for filename in source_dir.rglob(f"*.inc"):
+ filename.rename(filename.with_suffix(".h"))
+
+ def _process_autogenerated_inc_files(self, source_dir):
+ for filename in source_dir.rglob(f"*.inc"):
+ # Individual file fixes
+ if filename.stem == "gentab_ccitt":
+ with open(filename, "r+") as f:
+ content = f.read()
+ f.seek(0, 0)
+ f.write('#include "inttypes.h"\n' + content)
+
+ filename.rename(filename.with_suffix(".c"))
+
+ POSSIBLE_BASE_PATHS = ["src/standalone_crt/include/", "src/standalone_crt/crt_config/"]
+
+ def _find_modified_include_path(self, project_dir, file_path, import_path):
+ # If the import is for a .inc file we renamed to .c earlier, fix it
+ if import_path.endswith(self.CPP_FILE_EXTENSION_SYNONYMS):
+ import_path = re.sub(r"\.[a-z]+$", ".cpp", import_path)
+
+ if import_path.endswith(".inc"):
+ import_path = re.sub(r"\.[a-z]+$", ".h", import_path)
+
+ # If the import already works, don't modify it
+ if (file_path.parents[0] / import_path).exists():
+ return import_path
+
+ relative_path = file_path.relative_to(project_dir)
+ up_dirs_path = "../" * str(relative_path).count("/")
+
+ for base_path in self.POSSIBLE_BASE_PATHS:
+ full_potential_path = project_dir / base_path / import_path
+ if full_potential_path.exists():
+ new_include = up_dirs_path + base_path + import_path
+ return new_include
+
+ # If we can't find the file, just leave it untouched
+ # It's probably a standard C/C++ header
+ return import_path
+
+ # Arduino only supports imports relative to the top-level project,
Review comment:
put this into the docstring and include an example e.g.
here's an #include we get from µTVM:
`#include "../../etc"`
rewrite to:
`#include "src/..."`
also s/import/include/g
##########
File path: apps/microtvm/arduino/template_project/microtvm_api_server.py
##########
@@ -0,0 +1,423 @@
+import collections
+import functools
+import json
+import logging
+import os
+import os.path
+import pathlib
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tarfile
+from string import Template
+import tempfile
+import time
+
+import serial
+import serial.tools.list_ports
+
+from tvm.micro.project_api import server
+
+MODEL_LIBRARY_FORMAT_RELPATH = pathlib.Path("src") / "model" / "model.tar"
+API_SERVER_DIR = pathlib.Path(os.path.dirname(__file__) or os.path.getcwd())
+BUILD_DIR = API_SERVER_DIR / "build"
+MODEL_LIBRARY_FORMAT_PATH = API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH
+
+IS_TEMPLATE = not (API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH).exists()
+
+
+class InvalidPortException(Exception):
+ """Raised when the given port could not be opened"""
+
+
+class SketchUploadException(Exception):
+ """Raised when a sketch cannot be uploaded for an unknown reason."""
+
+
+class BoardAutodetectFailed(Exception):
+ """Raised when no attached hardware is found matching the requested board"""
+
+
+BOARD_PROPERTIES = {
+ "due": {"package": "arduino", "architecture": "sam", "board": "arduino_due_x"},
+ # Due to the way the Feather S2 bootloader works, compilation
+ # behaves fine but uploads cannot be done automatically
+ "feathers2": {
+ "package": "esp32",
+ "architecture": "esp32",
+ "board": "feathers2",
+ },
+ # Spresense only works as of its v2.3.0 sdk
+ "spresense": {
+ "package": "SPRESENSE",
+ "architecture": "spresense",
+ "board": "spresense",
+ },
+ "nano33ble": {
+ "package": "arduino",
+ "architecture": "mbed_nano",
+ "board": "nano33ble",
+ },
+ "pybadge": {
+ "package": "adafruit",
+ "architecture": "samd",
+ "board": "adafruit_pybadge_m4",
+ },
+ # The Teensy boards are listed here for completeness, but they
+ # won't work until https://github.com/arduino/arduino-cli/issues/700
+ # is finished
+ "teensy40": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy40",
+ },
+ "teensy41": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy41",
+ },
+}
+
+PROJECT_TYPES = ["example_project", "host_driven"]
+
+PROJECT_OPTIONS = [
+ server.ProjectOption(
+ "arduino_board",
+ choices=list(BOARD_PROPERTIES),
+ help="Name of the Arduino board to build for",
+ ),
+ server.ProjectOption("arduino_cli_cmd", help="Path to the arduino-cli tool."),
+ server.ProjectOption("port", help="Port to use for connecting to hardware"),
+ server.ProjectOption(
+ "example_project",
+ help="Type of project to generate.",
+ choices=tuple(PROJECT_TYPES),
+ ),
+ server.ProjectOption(
+ "verbose", help="True to pass --verbose flag to arduino-cli compile and upload"
+ ),
+]
+
+
+class Handler(server.ProjectAPIHandler):
+ def __init__(self):
+ super(Handler, self).__init__()
+ self._proc = None
+ self._port = None
+ self._serial = None
+
+ def server_info_query(self, tvm_version):
+ return server.ServerInfo(
+ platform_name="arduino",
+ is_template=IS_TEMPLATE,
+ model_library_format_path=MODEL_LIBRARY_FORMAT_PATH,
+ project_options=PROJECT_OPTIONS,
+ )
+
+ def _copy_project_files(self, api_server_dir, project_dir, project_type):
+ project_types_folder = api_server_dir.parents[0]
+ shutil.copytree(
+ project_types_folder / project_type / "src", project_dir / "src", dirs_exist_ok=True
+ )
+ # Arduino requires the .ino file have the same filename as its containing folder
+ shutil.copy2(
+ project_types_folder / project_type / "project.ino",
+ project_dir / f"{project_dir.stem}.ino",
+ )
+
+ CRT_COPY_ITEMS = ("include", "src")
+
+ def _copy_standalone_crt(self, source_dir, standalone_crt_dir):
+ # Copy over the standalone_crt directory
+ output_crt_dir = source_dir / "standalone_crt"
+ for item in self.CRT_COPY_ITEMS:
+ src_path = os.path.join(standalone_crt_dir, item)
+ dst_path = output_crt_dir / item
+ if os.path.isdir(src_path):
+ shutil.copytree(src_path, dst_path)
+ else:
+ shutil.copy2(src_path, dst_path)
+
+ # Example project is the "minimum viable project",
+ # and doesn't need a fancy RPC server
+ EXAMPLE_PROJECT_UNUSED_COMPONENTS = [
+ "include/dmlc",
+ "src/support",
+ "src/runtime/minrpc",
+ "src/runtime/crt/graph_executor",
+ "src/runtime/crt/microtvm_rpc_common",
+ "src/runtime/crt/microtvm_rpc_server",
+ "src/runtime/crt/tab",
+ ]
+
+ def _remove_unused_components(self, source_dir, project_type):
+ unused_components = []
+ if project_type == "example_project":
+ unused_components = self.EXAMPLE_PROJECT_UNUSED_COMPONENTS
+
+ for component in unused_components:
+ shutil.rmtree(source_dir / "standalone_crt" / component)
+
+ def _disassemble_mlf(self, mlf_tar_path, source_dir):
+ with tempfile.TemporaryDirectory() as mlf_unpacking_dir_str:
+ mlf_unpacking_dir = pathlib.Path(mlf_unpacking_dir_str)
+ with tarfile.open(mlf_tar_path, "r:") as tar:
+ tar.extractall(mlf_unpacking_dir)
+
+ model_dir = source_dir / "model"
+ model_dir.mkdir()
+
+ # Copy C files from model. The filesnames and quantity
+ # depend on the target string, so we just copy all c files
+ source_dir = mlf_unpacking_dir / "codegen" / "host" / "src"
+ for file in source_dir.rglob(f"*.c"):
+ shutil.copy(file, model_dir)
+
+ # Return metadata.json for use in templating
+ with open(os.path.join(mlf_unpacking_dir, "metadata.json")) as f:
+ metadata = json.load(f)
+ return metadata
+
+ def _template_model_header(self, source_dir, metadata):
+ with open(source_dir / "model.h", "r") as f:
+ model_h_template = Template(f.read())
+
+ template_values = {
+ "workspace_size_bytes": metadata["memory"]["functions"]["main"][0][
+ "workspace_size_bytes"
+ ],
+ }
+
+ with open(source_dir / "model.h", "w") as f:
+ f.write(model_h_template.substitute(template_values))
+
+ # Arduino ONLY recognizes .ino, .ccp, .c, .h
+
+ CPP_FILE_EXTENSION_SYNONYMS = ("cc", "cxx")
+
+ def _change_cpp_file_extensions(self, source_dir):
+ for ext in self.CPP_FILE_EXTENSION_SYNONYMS:
+ for filename in source_dir.rglob(f"*.{ext}"):
+ filename.rename(filename.with_suffix(".cpp"))
+
+ for filename in source_dir.rglob(f"*.inc"):
+ filename.rename(filename.with_suffix(".h"))
+
+ def _process_autogenerated_inc_files(self, source_dir):
+ for filename in source_dir.rglob(f"*.inc"):
+ # Individual file fixes
+ if filename.stem == "gentab_ccitt":
+ with open(filename, "r+") as f:
+ content = f.read()
+ f.seek(0, 0)
+ f.write('#include "inttypes.h"\n' + content)
+
+ filename.rename(filename.with_suffix(".c"))
+
+ POSSIBLE_BASE_PATHS = ["src/standalone_crt/include/", "src/standalone_crt/crt_config/"]
+
+ def _find_modified_include_path(self, project_dir, file_path, import_path):
Review comment:
this function should get a docstring/comment explaining what it does and why we need it
##########
File path: apps/microtvm/arduino/template_project/microtvm_api_server.py
##########
@@ -0,0 +1,423 @@
+import collections
+import functools
+import json
+import logging
+import os
+import os.path
+import pathlib
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tarfile
+from string import Template
+import tempfile
+import time
+
+import serial
+import serial.tools.list_ports
+
+from tvm.micro.project_api import server
+
+MODEL_LIBRARY_FORMAT_RELPATH = pathlib.Path("src") / "model" / "model.tar"
+API_SERVER_DIR = pathlib.Path(os.path.dirname(__file__) or os.path.getcwd())
+BUILD_DIR = API_SERVER_DIR / "build"
+MODEL_LIBRARY_FORMAT_PATH = API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH
+
+IS_TEMPLATE = not (API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH).exists()
+
+
+class InvalidPortException(Exception):
+ """Raised when the given port could not be opened"""
+
+
+class SketchUploadException(Exception):
+ """Raised when a sketch cannot be uploaded for an unknown reason."""
+
+
+class BoardAutodetectFailed(Exception):
+ """Raised when no attached hardware is found matching the requested board"""
+
+
+BOARD_PROPERTIES = {
+ "due": {"package": "arduino", "architecture": "sam", "board": "arduino_due_x"},
+ # Due to the way the Feather S2 bootloader works, compilation
+ # behaves fine but uploads cannot be done automatically
+ "feathers2": {
+ "package": "esp32",
+ "architecture": "esp32",
+ "board": "feathers2",
+ },
+ # Spresense only works as of its v2.3.0 sdk
+ "spresense": {
+ "package": "SPRESENSE",
+ "architecture": "spresense",
+ "board": "spresense",
+ },
+ "nano33ble": {
+ "package": "arduino",
+ "architecture": "mbed_nano",
+ "board": "nano33ble",
+ },
+ "pybadge": {
+ "package": "adafruit",
+ "architecture": "samd",
+ "board": "adafruit_pybadge_m4",
+ },
+ # The Teensy boards are listed here for completeness, but they
+ # won't work until https://github.com/arduino/arduino-cli/issues/700
+ # is finished
+ "teensy40": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy40",
+ },
+ "teensy41": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy41",
+ },
+}
+
+PROJECT_TYPES = ["example_project", "host_driven"]
+
+PROJECT_OPTIONS = [
+ server.ProjectOption(
+ "arduino_board",
+ choices=list(BOARD_PROPERTIES),
+ help="Name of the Arduino board to build for",
+ ),
+ server.ProjectOption("arduino_cli_cmd", help="Path to the arduino-cli tool."),
+ server.ProjectOption("port", help="Port to use for connecting to hardware"),
+ server.ProjectOption(
+ "example_project",
+ help="Type of project to generate.",
+ choices=tuple(PROJECT_TYPES),
+ ),
+ server.ProjectOption(
+ "verbose", help="True to pass --verbose flag to arduino-cli compile and upload"
+ ),
+]
+
+
+class Handler(server.ProjectAPIHandler):
+ def __init__(self):
+ super(Handler, self).__init__()
+ self._proc = None
+ self._port = None
+ self._serial = None
+
+ def server_info_query(self, tvm_version):
+ return server.ServerInfo(
+ platform_name="arduino",
+ is_template=IS_TEMPLATE,
+ model_library_format_path=MODEL_LIBRARY_FORMAT_PATH,
+ project_options=PROJECT_OPTIONS,
+ )
+
+ def _copy_project_files(self, api_server_dir, project_dir, project_type):
+ project_types_folder = api_server_dir.parents[0]
+ shutil.copytree(
+ project_types_folder / project_type / "src", project_dir / "src", dirs_exist_ok=True
+ )
+ # Arduino requires the .ino file have the same filename as its containing folder
+ shutil.copy2(
+ project_types_folder / project_type / "project.ino",
+ project_dir / f"{project_dir.stem}.ino",
+ )
+
+ CRT_COPY_ITEMS = ("include", "src")
+
+ def _copy_standalone_crt(self, source_dir, standalone_crt_dir):
+ # Copy over the standalone_crt directory
+ output_crt_dir = source_dir / "standalone_crt"
+ for item in self.CRT_COPY_ITEMS:
+ src_path = os.path.join(standalone_crt_dir, item)
+ dst_path = output_crt_dir / item
+ if os.path.isdir(src_path):
+ shutil.copytree(src_path, dst_path)
+ else:
+ shutil.copy2(src_path, dst_path)
+
+ # Example project is the "minimum viable project",
+ # and doesn't need a fancy RPC server
+ EXAMPLE_PROJECT_UNUSED_COMPONENTS = [
+ "include/dmlc",
+ "src/support",
+ "src/runtime/minrpc",
+ "src/runtime/crt/graph_executor",
+ "src/runtime/crt/microtvm_rpc_common",
+ "src/runtime/crt/microtvm_rpc_server",
+ "src/runtime/crt/tab",
+ ]
+
+ def _remove_unused_components(self, source_dir, project_type):
+ unused_components = []
+ if project_type == "example_project":
+ unused_components = self.EXAMPLE_PROJECT_UNUSED_COMPONENTS
+
+ for component in unused_components:
+ shutil.rmtree(source_dir / "standalone_crt" / component)
+
+ def _disassemble_mlf(self, mlf_tar_path, source_dir):
+ with tempfile.TemporaryDirectory() as mlf_unpacking_dir_str:
+ mlf_unpacking_dir = pathlib.Path(mlf_unpacking_dir_str)
+ with tarfile.open(mlf_tar_path, "r:") as tar:
+ tar.extractall(mlf_unpacking_dir)
+
+ model_dir = source_dir / "model"
+ model_dir.mkdir()
+
+ # Copy C files from model. The filesnames and quantity
+ # depend on the target string, so we just copy all c files
+ source_dir = mlf_unpacking_dir / "codegen" / "host" / "src"
+ for file in source_dir.rglob(f"*.c"):
+ shutil.copy(file, model_dir)
+
+ # Return metadata.json for use in templating
+ with open(os.path.join(mlf_unpacking_dir, "metadata.json")) as f:
+ metadata = json.load(f)
+ return metadata
+
+ def _template_model_header(self, source_dir, metadata):
+ with open(source_dir / "model.h", "r") as f:
+ model_h_template = Template(f.read())
+
+ template_values = {
+ "workspace_size_bytes": metadata["memory"]["functions"]["main"][0][
+ "workspace_size_bytes"
+ ],
+ }
+
+ with open(source_dir / "model.h", "w") as f:
+ f.write(model_h_template.substitute(template_values))
+
+ # Arduino ONLY recognizes .ino, .ccp, .c, .h
+
+ CPP_FILE_EXTENSION_SYNONYMS = ("cc", "cxx")
+
+ def _change_cpp_file_extensions(self, source_dir):
+ for ext in self.CPP_FILE_EXTENSION_SYNONYMS:
+ for filename in source_dir.rglob(f"*.{ext}"):
+ filename.rename(filename.with_suffix(".cpp"))
+
+ for filename in source_dir.rglob(f"*.inc"):
+ filename.rename(filename.with_suffix(".h"))
+
+ def _process_autogenerated_inc_files(self, source_dir):
+ for filename in source_dir.rglob(f"*.inc"):
+ # Individual file fixes
+ if filename.stem == "gentab_ccitt":
+ with open(filename, "r+") as f:
+ content = f.read()
+ f.seek(0, 0)
+ f.write('#include "inttypes.h"\n' + content)
+
+ filename.rename(filename.with_suffix(".c"))
+
+ POSSIBLE_BASE_PATHS = ["src/standalone_crt/include/", "src/standalone_crt/crt_config/"]
Review comment:
add a comment to explain this one
##########
File path: apps/microtvm/arduino/template_project/tests/test_arduino_microtvm_api_server.py
##########
@@ -0,0 +1,100 @@
+from unittest import mock
+from pathlib import Path
+import sys
+
+import pytest
+
+sys.path.insert(0, str(Path(__file__).parent.parent))
+import microtvm_api_server
+
+sys.path.pop(0)
+
+
+class TestGenerateProject:
+ DEFAULT_OPTIONS = {"arduino_cli_cmd": "arduino-cli", "arduino_board": "nano33ble"}
+
+ def test_print_c_array(self):
+ handler = microtvm_api_server.Handler()
+ c_arr = handler._print_c_array([1, 32, 32, 3])
+ assert c_arr == "{1, 32, 32, 3}"
+
+ def _set_pathlib_path_exists(self, value):
+ with mock.patch.object(Path, "exists") as mock_exists:
+ mock_exists.return_value = value
+
+ @mock.patch("pathlib.Path")
+ def test_find_modified_include_path(self, mock_pathlib_path):
+ handler = microtvm_api_server.Handler()
+
+ project_dir = mock_pathlib_path("/dummy/project")
+ file_path = (
+ project_dir
+ / "src"
+ / "standalone_crt"
+ / "src"
+ / "runtime"
+ / "crt"
+ / "graph_executor"
+ / "load_json.c"
+ )
+
+ # Should return C standard libs unmodified
+ clib_output = handler._find_modified_include_path(project_dir, file_path, "math.h")
+ assert clib_output == "math.h"
+
+ # If import already works, should return unmodified
+ valid_arduino_import = "../../../../include/tvm/runtime/crt/platform.h"
+ self._set_pathlib_path_exists(True)
+ valid_output = handler._find_modified_include_path(
+ project_dir, file_path, valid_arduino_import
+ )
+ assert valid_output == valid_arduino_import
+
+ BOARD_CONNECTED_OUTPUT = bytes(
+ "Port Type Board Name FQBN Core \n"
+ "/dev/ttyACM0 Serial Port (USB) Arduino Nano 33 BLE arduino:mbed_nano:nano33ble arduino:mbed_nano\n"
Review comment:
can you include an adversarial example with fqbn == "arduino:mbed_nano:nano33" and prove you can select it?
##########
File path: apps/microtvm/arduino/template_project/microtvm_api_server.py
##########
@@ -0,0 +1,423 @@
+import collections
+import functools
+import json
+import logging
+import os
+import os.path
+import pathlib
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tarfile
+from string import Template
+import tempfile
+import time
+
+import serial
+import serial.tools.list_ports
+
+from tvm.micro.project_api import server
+
+MODEL_LIBRARY_FORMAT_RELPATH = pathlib.Path("src") / "model" / "model.tar"
+API_SERVER_DIR = pathlib.Path(os.path.dirname(__file__) or os.path.getcwd())
+BUILD_DIR = API_SERVER_DIR / "build"
+MODEL_LIBRARY_FORMAT_PATH = API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH
+
+IS_TEMPLATE = not (API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH).exists()
+
+
+class InvalidPortException(Exception):
+ """Raised when the given port could not be opened"""
+
+
+class SketchUploadException(Exception):
+ """Raised when a sketch cannot be uploaded for an unknown reason."""
+
+
+class BoardAutodetectFailed(Exception):
+ """Raised when no attached hardware is found matching the requested board"""
+
+
+BOARD_PROPERTIES = {
+ "due": {"package": "arduino", "architecture": "sam", "board": "arduino_due_x"},
+ # Due to the way the Feather S2 bootloader works, compilation
+ # behaves fine but uploads cannot be done automatically
+ "feathers2": {
+ "package": "esp32",
+ "architecture": "esp32",
+ "board": "feathers2",
+ },
+ # Spresense only works as of its v2.3.0 sdk
+ "spresense": {
+ "package": "SPRESENSE",
+ "architecture": "spresense",
+ "board": "spresense",
+ },
+ "nano33ble": {
+ "package": "arduino",
+ "architecture": "mbed_nano",
+ "board": "nano33ble",
+ },
+ "pybadge": {
+ "package": "adafruit",
+ "architecture": "samd",
+ "board": "adafruit_pybadge_m4",
+ },
+ # The Teensy boards are listed here for completeness, but they
+ # won't work until https://github.com/arduino/arduino-cli/issues/700
+ # is finished
+ "teensy40": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy40",
+ },
+ "teensy41": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy41",
+ },
+}
+
+PROJECT_TYPES = ["example_project", "host_driven"]
+
+PROJECT_OPTIONS = [
+ server.ProjectOption(
+ "arduino_board",
+ choices=list(BOARD_PROPERTIES),
+ help="Name of the Arduino board to build for",
+ ),
+ server.ProjectOption("arduino_cli_cmd", help="Path to the arduino-cli tool."),
+ server.ProjectOption("port", help="Port to use for connecting to hardware"),
+ server.ProjectOption(
+ "example_project",
+ help="Type of project to generate.",
+ choices=tuple(PROJECT_TYPES),
+ ),
+ server.ProjectOption(
+ "verbose", help="True to pass --verbose flag to arduino-cli compile and upload"
+ ),
+]
+
+
+class Handler(server.ProjectAPIHandler):
+ def __init__(self):
+ super(Handler, self).__init__()
+ self._proc = None
+ self._port = None
+ self._serial = None
+
+ def server_info_query(self, tvm_version):
+ return server.ServerInfo(
+ platform_name="arduino",
+ is_template=IS_TEMPLATE,
+ model_library_format_path=MODEL_LIBRARY_FORMAT_PATH,
+ project_options=PROJECT_OPTIONS,
+ )
+
+ def _copy_project_files(self, api_server_dir, project_dir, project_type):
+ project_types_folder = api_server_dir.parents[0]
+ shutil.copytree(
+ project_types_folder / project_type / "src", project_dir / "src", dirs_exist_ok=True
+ )
+ # Arduino requires the .ino file have the same filename as its containing folder
+ shutil.copy2(
+ project_types_folder / project_type / "project.ino",
+ project_dir / f"{project_dir.stem}.ino",
+ )
+
+ CRT_COPY_ITEMS = ("include", "src")
+
+ def _copy_standalone_crt(self, source_dir, standalone_crt_dir):
+ # Copy over the standalone_crt directory
+ output_crt_dir = source_dir / "standalone_crt"
+ for item in self.CRT_COPY_ITEMS:
+ src_path = os.path.join(standalone_crt_dir, item)
+ dst_path = output_crt_dir / item
+ if os.path.isdir(src_path):
+ shutil.copytree(src_path, dst_path)
+ else:
+ shutil.copy2(src_path, dst_path)
+
+ # Example project is the "minimum viable project",
+ # and doesn't need a fancy RPC server
+ EXAMPLE_PROJECT_UNUSED_COMPONENTS = [
+ "include/dmlc",
+ "src/support",
+ "src/runtime/minrpc",
+ "src/runtime/crt/graph_executor",
+ "src/runtime/crt/microtvm_rpc_common",
+ "src/runtime/crt/microtvm_rpc_server",
+ "src/runtime/crt/tab",
+ ]
+
+ def _remove_unused_components(self, source_dir, project_type):
+ unused_components = []
+ if project_type == "example_project":
+ unused_components = self.EXAMPLE_PROJECT_UNUSED_COMPONENTS
+
+ for component in unused_components:
+ shutil.rmtree(source_dir / "standalone_crt" / component)
+
+ def _disassemble_mlf(self, mlf_tar_path, source_dir):
+ with tempfile.TemporaryDirectory() as mlf_unpacking_dir_str:
+ mlf_unpacking_dir = pathlib.Path(mlf_unpacking_dir_str)
+ with tarfile.open(mlf_tar_path, "r:") as tar:
+ tar.extractall(mlf_unpacking_dir)
+
+ model_dir = source_dir / "model"
+ model_dir.mkdir()
+
+ # Copy C files from model. The filesnames and quantity
+ # depend on the target string, so we just copy all c files
+ source_dir = mlf_unpacking_dir / "codegen" / "host" / "src"
+ for file in source_dir.rglob(f"*.c"):
+ shutil.copy(file, model_dir)
+
+ # Return metadata.json for use in templating
+ with open(os.path.join(mlf_unpacking_dir, "metadata.json")) as f:
+ metadata = json.load(f)
+ return metadata
+
+ def _template_model_header(self, source_dir, metadata):
+ with open(source_dir / "model.h", "r") as f:
+ model_h_template = Template(f.read())
+
+ template_values = {
+ "workspace_size_bytes": metadata["memory"]["functions"]["main"][0][
+ "workspace_size_bytes"
+ ],
+ }
+
+ with open(source_dir / "model.h", "w") as f:
+ f.write(model_h_template.substitute(template_values))
+
+ # Arduino ONLY recognizes .ino, .ccp, .c, .h
+
+ CPP_FILE_EXTENSION_SYNONYMS = ("cc", "cxx")
+
+ def _change_cpp_file_extensions(self, source_dir):
+ for ext in self.CPP_FILE_EXTENSION_SYNONYMS:
+ for filename in source_dir.rglob(f"*.{ext}"):
+ filename.rename(filename.with_suffix(".cpp"))
+
+ for filename in source_dir.rglob(f"*.inc"):
+ filename.rename(filename.with_suffix(".h"))
+
+ def _process_autogenerated_inc_files(self, source_dir):
+ for filename in source_dir.rglob(f"*.inc"):
+ # Individual file fixes
+ if filename.stem == "gentab_ccitt":
+ with open(filename, "r+") as f:
+ content = f.read()
+ f.seek(0, 0)
+ f.write('#include "inttypes.h"\n' + content)
+
+ filename.rename(filename.with_suffix(".c"))
+
+ POSSIBLE_BASE_PATHS = ["src/standalone_crt/include/", "src/standalone_crt/crt_config/"]
+
+ def _find_modified_include_path(self, project_dir, file_path, import_path):
+ # If the import is for a .inc file we renamed to .c earlier, fix it
+ if import_path.endswith(self.CPP_FILE_EXTENSION_SYNONYMS):
+ import_path = re.sub(r"\.[a-z]+$", ".cpp", import_path)
+
+ if import_path.endswith(".inc"):
+ import_path = re.sub(r"\.[a-z]+$", ".h", import_path)
+
+ # If the import already works, don't modify it
+ if (file_path.parents[0] / import_path).exists():
+ return import_path
+
+ relative_path = file_path.relative_to(project_dir)
+ up_dirs_path = "../" * str(relative_path).count("/")
+
+ for base_path in self.POSSIBLE_BASE_PATHS:
+ full_potential_path = project_dir / base_path / import_path
+ if full_potential_path.exists():
+ new_include = up_dirs_path + base_path + import_path
+ return new_include
+
+ # If we can't find the file, just leave it untouched
+ # It's probably a standard C/C++ header
+ return import_path
+
+ # Arduino only supports imports relative to the top-level project,
+ # so we need to adjust each import to meet this convention
+ def _convert_imports(self, project_dir, source_dir):
+ for ext in ("c", "h", "cpp"):
+ for filename in source_dir.rglob(f"*.{ext}"):
+ with filename.open() as file:
+ lines = file.readlines()
+
+ for i in range(len(lines)):
+ # Check if line has an include
+ result = re.search(r"#include\s*[<\"]([^>]*)[>\"]", lines[i])
+ if not result:
+ continue
+ new_include = self._find_modified_include_path(
+ project_dir, filename, result.groups()[0]
+ )
+
+ lines[i] = f'#include "{new_include}"\n'
+
+ with filename.open("w") as file:
+ file.writelines(lines)
+
+ def generate_project(self, model_library_format_path, standalone_crt_dir, project_dir, options):
+ # Reference key directories with pathlib
+ project_dir = pathlib.Path(project_dir)
+ project_dir.mkdir()
+ source_dir = project_dir / "src"
+ source_dir.mkdir()
+
+ # Copies files from the template folder to project_dir. model.h is copied here,
+ # but will also need to be templated later.
+ if IS_TEMPLATE:
+ shutil.copy2(API_SERVER_DIR / "microtvm_api_server.py", project_dir)
+ self._copy_project_files(API_SERVER_DIR, project_dir, options["project_type"])
+
+ # Copy standalone_crt into src folder
+ self._copy_standalone_crt(source_dir, standalone_crt_dir)
+ self._remove_unused_components(source_dir, options["project_type"])
+
+ # Unpack the MLF and copy the relevant files
+ metadata = self._disassemble_mlf(model_library_format_path, source_dir)
+ shutil.copy2(model_library_format_path, source_dir / "model")
+
+ # For AOT, template model.h with metadata to minimize space usage
+ if options["project_type"] == "example_project":
+ self._template_model_header(source_dir, metadata)
+
+ self._change_cpp_file_extensions(source_dir)
+
+ # Recursively change imports
+ self._convert_imports(project_dir, source_dir)
+
+ def _get_fqbn(self, options):
+ o = BOARD_PROPERTIES[options["arduino_board"]]
+ return f"{o['package']}:{o['architecture']}:{o['board']}"
+
+ def build(self, options):
+ BUILD_DIR.mkdir()
+ print(BUILD_DIR)
+
+ compile_cmd = [
+ options["arduino_cli_cmd"],
+ "compile",
+ "./project/",
+ "--fqbn",
+ self._get_fqbn(options),
+ "--build-path",
+ BUILD_DIR.resolve(),
+ ]
+
+ if options.get("verbose"):
+ compile_cmd.append("--verbose")
+
+ # Specify project to compile
+ output = subprocess.check_call(compile_cmd)
+ assert output == 0
+
+ # We run the command `arduino-cli board list`, which produces
+ # outputs of the form:
+ """
+ Port Type Board Name FQBN Core
+ /dev/ttyS4 Serial Port Unknown
+ /dev/ttyUSB0 Serial Port (USB) Spresense SPRESENSE:spresense:spresense SPRESENSE:spresense
+ """
+
+ def _auto_detect_port(self, options):
+ list_cmd = [options["arduino_cli_cmd"], "board", "list"]
+ list_cmd_output = subprocess.check_output(list_cmd).decode("utf-8")
+ # Remove header and new lines at bottom
+ port_options = list_cmd_output.split("\n")[1:-2]
+
+ # Select the first compatible board
+ fqbn = self._get_fqbn(options)
+ for port_option in port_options:
+ if fqbn in port_option:
+ return port_option.split(" ")[0]
+
+ # If no compatible boards, raise an error
+ raise BoardAutodetectFailed()
+
+ def _get_arduino_port(self, options):
+ if not self._port:
+ if "port" in options and options["port"]:
+ self._port = options["port"]
+ else:
+ self._port = self._auto_detect_port(options)
+
+ return self._port
+
+ def flash(self, options):
+ port = self._get_arduino_port(options)
+
+ upload_cmd = [
+ options["arduino_cli_cmd"],
+ "upload",
+ "./project",
+ "--fqbn",
+ self._get_fqbn(options),
+ "--input-dir",
+ BUILD_DIR.resolve(),
+ "--port",
+ port,
+ ]
+
+ if options.get("verbose"):
+ upload_cmd.append("--verbose")
+
+ output = subprocess.check_call(upload_cmd)
Review comment:
i think check_call should raise its own exception if the process exits non-zero. can you switch to call() or another thing if you're going to raise the custom exceptions? or just delete the custom exceptions
##########
File path: tests/micro/arduino/conftest.py
##########
@@ -0,0 +1,97 @@
+import datetime
+import pathlib
+
+import pytest
+
+import tvm.target.target
+
+# The models that should pass this configuration. Maps a short, identifying platform string to
+# (model, zephyr_board).
+PLATFORMS = {
+ "due": ("sam3x8e", "due"),
+ "feathers2": ("esp32", "feathers2"),
+ "nano33ble": ("nrf52840", "nano33ble"),
+ "pybadge": ("atsamd51", "pybadge"),
+ "spresense": ("cxd5602gg", "spresense"),
+ "teensy40": ("imxrt1060", "teensy40"),
+ "teensy41": ("imxrt1060", "teensy41"),
+}
+
+TEMPLATE_PROJECT_DIR = (
+ pathlib.Path(__file__).parent
+ / ".."
+ / ".."
+ / ".."
+ / "apps"
+ / "microtvm"
+ / "arduino"
+ / "template_project"
+).resolve()
+
+
+def pytest_addoption(parser):
+ parser.addoption(
+ "--microtvm-platforms",
+ default=["due"],
Review comment:
for checkin, should avoid preferring any one hardware platform (a software-only one is ok, since that makes the test always run out of the box; a hardware one should require manual confirmation from the user that this is the board they've connected)
##########
File path: tests/micro/arduino/conftest.py
##########
@@ -0,0 +1,97 @@
+import datetime
+import pathlib
+
+import pytest
+
+import tvm.target.target
+
+# The models that should pass this configuration. Maps a short, identifying platform string to
+# (model, zephyr_board).
+PLATFORMS = {
+ "due": ("sam3x8e", "due"),
+ "feathers2": ("esp32", "feathers2"),
+ "nano33ble": ("nrf52840", "nano33ble"),
+ "pybadge": ("atsamd51", "pybadge"),
+ "spresense": ("cxd5602gg", "spresense"),
+ "teensy40": ("imxrt1060", "teensy40"),
+ "teensy41": ("imxrt1060", "teensy41"),
+}
+
+TEMPLATE_PROJECT_DIR = (
+ pathlib.Path(__file__).parent
+ / ".."
+ / ".."
+ / ".."
+ / "apps"
+ / "microtvm"
+ / "arduino"
+ / "template_project"
+).resolve()
+
+
+def pytest_addoption(parser):
+ parser.addoption(
+ "--microtvm-platforms",
+ default=["due"],
+ nargs="*",
+ choices=PLATFORMS.keys(),
+ help="Target platforms for microTVM tests.",
+ )
+ parser.addoption(
+ "--arduino-cli-cmd",
+ default="arduino-cli",
+ help="Path to `arduino-cli` command for flashing device.",
+ )
+ parser.addoption(
Review comment:
i'd almost propose naming this --test-build-only and add this flag to tests/scripts/task_python_microtvm.sh to make it obvious we aren't running the full test in CI
##########
File path: tests/micro/arduino/conftest.py
##########
@@ -0,0 +1,97 @@
+import datetime
+import pathlib
+
+import pytest
+
+import tvm.target.target
+
+# The models that should pass this configuration. Maps a short, identifying platform string to
+# (model, zephyr_board).
+PLATFORMS = {
+ "due": ("sam3x8e", "due"),
+ "feathers2": ("esp32", "feathers2"),
+ "nano33ble": ("nrf52840", "nano33ble"),
+ "pybadge": ("atsamd51", "pybadge"),
+ "spresense": ("cxd5602gg", "spresense"),
+ "teensy40": ("imxrt1060", "teensy40"),
+ "teensy41": ("imxrt1060", "teensy41"),
+}
+
+TEMPLATE_PROJECT_DIR = (
+ pathlib.Path(__file__).parent
+ / ".."
+ / ".."
+ / ".."
+ / "apps"
+ / "microtvm"
+ / "arduino"
+ / "template_project"
+).resolve()
+
+
+def pytest_addoption(parser):
+ parser.addoption(
+ "--microtvm-platforms",
+ default=["due"],
+ nargs="*",
+ choices=PLATFORMS.keys(),
+ help="Target platforms for microTVM tests.",
+ )
+ parser.addoption(
+ "--arduino-cli-cmd",
+ default="arduino-cli",
+ help="Path to `arduino-cli` command for flashing device.",
+ )
+ parser.addoption(
+ "--run-hardware-tests",
+ action="store_true",
+ help="Run tests that require physical hardware.",
+ )
+ parser.addoption(
+ "--tvm-debug",
+ action="store_true",
+ default=False,
+ help="If set true, 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>",
Review comment:
"If given, "
##########
File path: apps/microtvm/arduino/template_project/microtvm_api_server.py
##########
@@ -0,0 +1,423 @@
+import collections
+import functools
+import json
+import logging
+import os
+import os.path
+import pathlib
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tarfile
+from string import Template
+import tempfile
+import time
+
+import serial
+import serial.tools.list_ports
+
+from tvm.micro.project_api import server
+
+MODEL_LIBRARY_FORMAT_RELPATH = pathlib.Path("src") / "model" / "model.tar"
+API_SERVER_DIR = pathlib.Path(os.path.dirname(__file__) or os.path.getcwd())
+BUILD_DIR = API_SERVER_DIR / "build"
+MODEL_LIBRARY_FORMAT_PATH = API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH
+
+IS_TEMPLATE = not (API_SERVER_DIR / MODEL_LIBRARY_FORMAT_RELPATH).exists()
+
+
+class InvalidPortException(Exception):
+ """Raised when the given port could not be opened"""
+
+
+class SketchUploadException(Exception):
+ """Raised when a sketch cannot be uploaded for an unknown reason."""
+
+
+class BoardAutodetectFailed(Exception):
+ """Raised when no attached hardware is found matching the requested board"""
+
+
+BOARD_PROPERTIES = {
+ "due": {"package": "arduino", "architecture": "sam", "board": "arduino_due_x"},
+ # Due to the way the Feather S2 bootloader works, compilation
+ # behaves fine but uploads cannot be done automatically
+ "feathers2": {
+ "package": "esp32",
+ "architecture": "esp32",
+ "board": "feathers2",
+ },
+ # Spresense only works as of its v2.3.0 sdk
+ "spresense": {
+ "package": "SPRESENSE",
+ "architecture": "spresense",
+ "board": "spresense",
+ },
+ "nano33ble": {
+ "package": "arduino",
+ "architecture": "mbed_nano",
+ "board": "nano33ble",
+ },
+ "pybadge": {
+ "package": "adafruit",
+ "architecture": "samd",
+ "board": "adafruit_pybadge_m4",
+ },
+ # The Teensy boards are listed here for completeness, but they
+ # won't work until https://github.com/arduino/arduino-cli/issues/700
+ # is finished
+ "teensy40": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy40",
+ },
+ "teensy41": {
+ "package": "teensy",
+ "architecture": "avr",
+ "board": "teensy41",
+ },
+}
+
+PROJECT_TYPES = ["example_project", "host_driven"]
+
+PROJECT_OPTIONS = [
+ server.ProjectOption(
+ "arduino_board",
+ choices=list(BOARD_PROPERTIES),
+ help="Name of the Arduino board to build for",
+ ),
+ server.ProjectOption("arduino_cli_cmd", help="Path to the arduino-cli tool."),
+ server.ProjectOption("port", help="Port to use for connecting to hardware"),
+ server.ProjectOption(
+ "example_project",
+ help="Type of project to generate.",
+ choices=tuple(PROJECT_TYPES),
+ ),
+ server.ProjectOption(
+ "verbose", help="True to pass --verbose flag to arduino-cli compile and upload"
+ ),
+]
+
+
+class Handler(server.ProjectAPIHandler):
+ def __init__(self):
+ super(Handler, self).__init__()
+ self._proc = None
+ self._port = None
+ self._serial = None
+
+ def server_info_query(self, tvm_version):
+ return server.ServerInfo(
+ platform_name="arduino",
+ is_template=IS_TEMPLATE,
+ model_library_format_path=MODEL_LIBRARY_FORMAT_PATH,
+ project_options=PROJECT_OPTIONS,
+ )
+
+ def _copy_project_files(self, api_server_dir, project_dir, project_type):
+ project_types_folder = api_server_dir.parents[0]
+ shutil.copytree(
+ project_types_folder / project_type / "src", project_dir / "src", dirs_exist_ok=True
+ )
+ # Arduino requires the .ino file have the same filename as its containing folder
+ shutil.copy2(
+ project_types_folder / project_type / "project.ino",
+ project_dir / f"{project_dir.stem}.ino",
+ )
+
+ CRT_COPY_ITEMS = ("include", "src")
+
+ def _copy_standalone_crt(self, source_dir, standalone_crt_dir):
+ # Copy over the standalone_crt directory
+ output_crt_dir = source_dir / "standalone_crt"
+ for item in self.CRT_COPY_ITEMS:
+ src_path = os.path.join(standalone_crt_dir, item)
+ dst_path = output_crt_dir / item
+ if os.path.isdir(src_path):
+ shutil.copytree(src_path, dst_path)
+ else:
+ shutil.copy2(src_path, dst_path)
+
+ # Example project is the "minimum viable project",
+ # and doesn't need a fancy RPC server
+ EXAMPLE_PROJECT_UNUSED_COMPONENTS = [
+ "include/dmlc",
+ "src/support",
+ "src/runtime/minrpc",
+ "src/runtime/crt/graph_executor",
+ "src/runtime/crt/microtvm_rpc_common",
+ "src/runtime/crt/microtvm_rpc_server",
+ "src/runtime/crt/tab",
+ ]
+
+ def _remove_unused_components(self, source_dir, project_type):
+ unused_components = []
+ if project_type == "example_project":
+ unused_components = self.EXAMPLE_PROJECT_UNUSED_COMPONENTS
+
+ for component in unused_components:
+ shutil.rmtree(source_dir / "standalone_crt" / component)
+
+ def _disassemble_mlf(self, mlf_tar_path, source_dir):
+ with tempfile.TemporaryDirectory() as mlf_unpacking_dir_str:
+ mlf_unpacking_dir = pathlib.Path(mlf_unpacking_dir_str)
+ with tarfile.open(mlf_tar_path, "r:") as tar:
+ tar.extractall(mlf_unpacking_dir)
+
+ model_dir = source_dir / "model"
+ model_dir.mkdir()
+
+ # Copy C files from model. The filesnames and quantity
+ # depend on the target string, so we just copy all c files
+ source_dir = mlf_unpacking_dir / "codegen" / "host" / "src"
+ for file in source_dir.rglob(f"*.c"):
+ shutil.copy(file, model_dir)
+
+ # Return metadata.json for use in templating
+ with open(os.path.join(mlf_unpacking_dir, "metadata.json")) as f:
+ metadata = json.load(f)
+ return metadata
+
+ def _template_model_header(self, source_dir, metadata):
+ with open(source_dir / "model.h", "r") as f:
+ model_h_template = Template(f.read())
+
+ template_values = {
+ "workspace_size_bytes": metadata["memory"]["functions"]["main"][0][
+ "workspace_size_bytes"
+ ],
+ }
+
+ with open(source_dir / "model.h", "w") as f:
+ f.write(model_h_template.substitute(template_values))
+
+ # Arduino ONLY recognizes .ino, .ccp, .c, .h
+
+ CPP_FILE_EXTENSION_SYNONYMS = ("cc", "cxx")
+
+ def _change_cpp_file_extensions(self, source_dir):
+ for ext in self.CPP_FILE_EXTENSION_SYNONYMS:
+ for filename in source_dir.rglob(f"*.{ext}"):
+ filename.rename(filename.with_suffix(".cpp"))
+
+ for filename in source_dir.rglob(f"*.inc"):
+ filename.rename(filename.with_suffix(".h"))
+
+ def _process_autogenerated_inc_files(self, source_dir):
+ for filename in source_dir.rglob(f"*.inc"):
+ # Individual file fixes
+ if filename.stem == "gentab_ccitt":
+ with open(filename, "r+") as f:
+ content = f.read()
+ f.seek(0, 0)
+ f.write('#include "inttypes.h"\n' + content)
+
+ filename.rename(filename.with_suffix(".c"))
+
+ POSSIBLE_BASE_PATHS = ["src/standalone_crt/include/", "src/standalone_crt/crt_config/"]
+
+ def _find_modified_include_path(self, project_dir, file_path, import_path):
+ # If the import is for a .inc file we renamed to .c earlier, fix it
+ if import_path.endswith(self.CPP_FILE_EXTENSION_SYNONYMS):
+ import_path = re.sub(r"\.[a-z]+$", ".cpp", import_path)
+
+ if import_path.endswith(".inc"):
+ import_path = re.sub(r"\.[a-z]+$", ".h", import_path)
+
+ # If the import already works, don't modify it
+ if (file_path.parents[0] / import_path).exists():
+ return import_path
+
+ relative_path = file_path.relative_to(project_dir)
+ up_dirs_path = "../" * str(relative_path).count("/")
+
+ for base_path in self.POSSIBLE_BASE_PATHS:
+ full_potential_path = project_dir / base_path / import_path
+ if full_potential_path.exists():
+ new_include = up_dirs_path + base_path + import_path
+ return new_include
+
+ # If we can't find the file, just leave it untouched
+ # It's probably a standard C/C++ header
+ return import_path
+
+ # Arduino only supports imports relative to the top-level project,
+ # so we need to adjust each import to meet this convention
+ def _convert_imports(self, project_dir, source_dir):
+ for ext in ("c", "h", "cpp"):
+ for filename in source_dir.rglob(f"*.{ext}"):
+ with filename.open() as file:
+ lines = file.readlines()
+
+ for i in range(len(lines)):
+ # Check if line has an include
+ result = re.search(r"#include\s*[<\"]([^>]*)[>\"]", lines[i])
+ if not result:
+ continue
+ new_include = self._find_modified_include_path(
+ project_dir, filename, result.groups()[0]
+ )
+
+ lines[i] = f'#include "{new_include}"\n'
+
+ with filename.open("w") as file:
+ file.writelines(lines)
+
+ def generate_project(self, model_library_format_path, standalone_crt_dir, project_dir, options):
+ # Reference key directories with pathlib
+ project_dir = pathlib.Path(project_dir)
+ project_dir.mkdir()
+ source_dir = project_dir / "src"
+ source_dir.mkdir()
+
+ # Copies files from the template folder to project_dir. model.h is copied here,
+ # but will also need to be templated later.
+ if IS_TEMPLATE:
+ shutil.copy2(API_SERVER_DIR / "microtvm_api_server.py", project_dir)
+ self._copy_project_files(API_SERVER_DIR, project_dir, options["project_type"])
+
+ # Copy standalone_crt into src folder
+ self._copy_standalone_crt(source_dir, standalone_crt_dir)
+ self._remove_unused_components(source_dir, options["project_type"])
+
+ # Unpack the MLF and copy the relevant files
+ metadata = self._disassemble_mlf(model_library_format_path, source_dir)
+ shutil.copy2(model_library_format_path, source_dir / "model")
+
+ # For AOT, template model.h with metadata to minimize space usage
+ if options["project_type"] == "example_project":
+ self._template_model_header(source_dir, metadata)
+
+ self._change_cpp_file_extensions(source_dir)
+
+ # Recursively change imports
+ self._convert_imports(project_dir, source_dir)
+
+ def _get_fqbn(self, options):
+ o = BOARD_PROPERTIES[options["arduino_board"]]
+ return f"{o['package']}:{o['architecture']}:{o['board']}"
+
+ def build(self, options):
+ BUILD_DIR.mkdir()
+ print(BUILD_DIR)
Review comment:
nit: delete or switch to logging
##########
File path: tests/micro/arduino/test_arduino_rpc_server.py
##########
@@ -0,0 +1,352 @@
+import datetime
+import os
+import pathlib
+import shutil
+import sys
+import time
+
+import numpy as np
+import onnx
+from PIL import Image
+import pytest
+import tflite
+
+import tvm
+from tvm import micro, relay
+from tvm.relay.testing import byoc
+
+import conftest
+
+"""
Review comment:
move to the top of the file above imports; asf header
##########
File path: tests/micro/arduino/test_arduino_rpc_server.py
##########
@@ -0,0 +1,352 @@
+import datetime
+import os
+import pathlib
+import shutil
+import sys
+import time
+
+import numpy as np
+import onnx
+from PIL import Image
+import pytest
+import tflite
+
+import tvm
+from tvm import micro, relay
+from tvm.relay.testing import byoc
+
+import conftest
+
+"""
+This unit test simulates an autotuning workflow, where we:
+1. Instantiate the Arduino RPC server project
+2. Build and flash that project onto our target board
+
+"""
+
+# We'll make a new workspace for each test
+@pytest.fixture(scope="function")
+def workspace_dir(platform, run_hardware_tests):
+ if not run_hardware_tests:
Review comment:
can you make this a decorator like tvm.testing.requires_micro? just declare the decorator in conftest.py
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@tvm.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org