diff --git a/apps/microtvm/arduino/example_project/src/model.c b/apps/microtvm/arduino/example_project/src/model.c index 788dcccd2728..3268e5961bc9 100644 --- a/apps/microtvm/arduino/example_project/src/model.c +++ b/apps/microtvm/arduino/example_project/src/model.c @@ -15,7 +15,7 @@ tvm_workspace_t app_workspace; // Blink code for debugging purposes void TVMPlatformAbort(tvm_crt_error_t error) { for (;;) { - #ifdef LED_BUILTIN +#ifdef LED_BUILTIN digitalWrite(LED_BUILTIN, HIGH); delay(250); digitalWrite(LED_BUILTIN, LOW); @@ -24,7 +24,7 @@ void TVMPlatformAbort(tvm_crt_error_t error) { delay(250); digitalWrite(LED_BUILTIN, LOW); delay(750); - #endif +#endif } } diff --git a/apps/microtvm/arduino/template_project/microtvm_api_server.py b/apps/microtvm/arduino/template_project/microtvm_api_server.py index 8ac670575376..44088cec2064 100644 --- a/apps/microtvm/arduino/template_project/microtvm_api_server.py +++ b/apps/microtvm/arduino/template_project/microtvm_api_server.py @@ -41,11 +41,7 @@ class BoardAutodetectFailed(Exception): BOARD_PROPERTIES = { - "due": { - "package": "arduino", - "architecture": "sam", - "board": "arduino_due_x" - }, + "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": { @@ -112,7 +108,7 @@ def __init__(self): self._port = None self._serial = None - def server_info_query(self): + def server_info_query(self, tvm_version): return server.ServerInfo( platform_name="arduino", is_template=IS_TEMPLATE, @@ -372,7 +368,7 @@ def flash(self, options): ] if options.get("verbose"): - compile_cmd.append("--verbose") + upload_cmd.append("--verbose") output = subprocess.check_call(upload_cmd) diff --git a/python/tvm/target/target.py b/python/tvm/target/target.py index eedd7879cc03..559e7b3b28d3 100644 --- a/python/tvm/target/target.py +++ b/python/tvm/target/target.py @@ -314,7 +314,6 @@ def micro(model="unknown", options=None): ) if (not options) or (options and not any("-executor=aot" in o for o in options)): - print("Adding system libs!") opts = _merge_opts(opts, "--system-lib") # NOTE: in the future, the default micro target will be LLVM except when diff --git a/tests/micro/arduino/conftest.py b/tests/micro/arduino/conftest.py index 9a0d3623cb89..a4ab46dceafb 100644 --- a/tests/micro/arduino/conftest.py +++ b/tests/micro/arduino/conftest.py @@ -1,3 +1,6 @@ +import datetime +import pathlib + import pytest import tvm.target.target @@ -14,10 +17,21 @@ "teensy41": ("imxrt1060", "teensy41"), } +TEMPLATE_PROJECT_DIR = ( + pathlib.Path(__file__).parent + / ".." + / ".." + / ".." + / "apps" + / "microtvm" + / "arduino" + / "template_project" +).resolve() + def pytest_addoption(parser): parser.addoption( - "--platforms", + "--microtvm-platforms", default=["due"], nargs="*", choices=PLATFORMS.keys(), @@ -33,12 +47,19 @@ def pytest_addoption(parser): 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: ", + ) + # We might do project generation differently for different boards in the future # (to take advantage of multiple cores / external memory / etc.), so all tests # are parameterized by board def pytest_generate_tests(metafunc): - platforms = metafunc.config.getoption("platforms") + platforms = metafunc.config.getoption("microtvm_platforms") metafunc.parametrize("platform", platforms, scope="session") @@ -47,6 +68,30 @@ def arduino_cli_cmd(request): return request.config.getoption("--arduino-cli-cmd") +@pytest.fixture(scope="session") +def tvm_debug(request): + return request.config.getoption("--tvm-debug") + + @pytest.fixture(scope="session") def run_hardware_tests(request): return request.config.getoption("--run-hardware-tests") + + +def make_workspace_dir(test_name, platform): + _, arduino_board = PLATFORMS[platform] + filepath = pathlib.Path(__file__) + board_workspace = ( + filepath.parent + / f"workspace_{test_name}_{arduino_board}" + / datetime.datetime.now().strftime("%Y-%m-%dT%H-%M-%S") + ) + + number = 0 + while board_workspace.exists(): + number += 1 + board_workspace = pathlib.Path(str(board_workspace) + f"-{number}") + board_workspace.parent.mkdir(exist_ok=True, parents=True) + t = tvm.contrib.utils.tempdir(board_workspace) + # time.sleep(200) + return t diff --git a/tests/micro/arduino/test_arduino_rpc_server.py b/tests/micro/arduino/test_arduino_rpc_server.py index 7d274eb77240..0b5ba07b8b33 100644 --- a/tests/micro/arduino/test_arduino_rpc_server.py +++ b/tests/micro/arduino/test_arduino_rpc_server.py @@ -24,43 +24,25 @@ """ -PLATFORMS = conftest.PLATFORMS +# 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, mod): - parent_dir = os.path.dirname(__file__) - filename = os.path.splitext(os.path.basename(__file__))[0] - prev_build = ( - f"{os.path.join(parent_dir, 'archive')}_{filename}_{arduino_board}_last_build.micro" - ) - workspace_root = os.path.join( - f"{os.path.join(parent_dir, 'workspace')}_{filename}_{arduino_board}", - datetime.datetime.now().strftime("%Y-%m-%dT%H-%M-%S"), - ) - workspace_parent = os.path.dirname(workspace_root) - if not os.path.exists(workspace_parent): - os.makedirs(workspace_parent) - workspace = tvm.micro.Workspace(debug=False, root=workspace_root) - - template_project_dir = ( - pathlib.Path(__file__).parent - / ".." - / ".." - / ".." - / "apps" - / "microtvm" - / "arduino" - / "template_project" - ).resolve() + +def _make_session(model, arduino_board, arduino_cli_cmd, workspace_dir, mod, build_config): project = tvm.micro.generate_project( - str(template_project_dir), + str(conftest.TEMPLATE_PROJECT_DIR), mod, - workspace.relpath("project"), + workspace_dir / "project", { "arduino_board": arduino_board, "arduino_cli_cmd": arduino_cli_cmd, "project_type": "host_driven", - "verbose": 0, + "verbose": bool(build_config.get("debug")), }, ) project.build() @@ -68,28 +50,33 @@ def _make_session(model, arduino_board, arduino_cli_cmd, mod): return tvm.micro.Session(project.transport()) -def _make_sess_from_op(model, arduino_board, arduino_cli_cmd, op_name, sched, arg_bufs): +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, mod) + return _make_session(model, arduino_board, arduino_cli_cmd, workspace_dir, mod, build_config) -def _make_add_sess(model, arduino_board, arduino_cli_cmd): +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, "add", sched, [A, B, C]) + 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): +def test_compile_runtime(platform, arduino_cli_cmd, tvm_debug, workspace_dir): """Test compiling the on-device runtime.""" - model, arduino_board = PLATFORMS[platform] + 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): @@ -104,15 +91,17 @@ def test_basic_add(sess): 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) as sess: + with _make_add_sess(model, arduino_board, arduino_cli_cmd, workspace_dir, build_config) as sess: test_basic_add(sess) + print(workspace_dir) @tvm.testing.requires_micro -def test_platform_timer(platform, arduino_cli_cmd): +def test_platform_timer(platform, arduino_cli_cmd, tvm_debug, workspace_dir): """Test compiling the on-device runtime.""" - model, arduino_board = PLATFORMS[platform] + 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): @@ -132,14 +121,16 @@ def test_basic_add(sess): assert result.mean > 0 assert len(result.results) == 3 - with _make_add_sess(model, arduino_board, arduino_cli_cmd) as sess: + with _make_add_sess(model, arduino_board, arduino_cli_cmd, workspace_dir, build_config) as sess: test_basic_add(sess) @tvm.testing.requires_micro -def test_relay(platform, arduino_cli_cmd): +def test_relay(platform, arduino_cli_cmd, tvm_debug, workspace_dir): """Testing a simple relay graph""" - model, arduino_board = PLATFORMS[platform] + model, arduino_board = conftest.PLATFORMS[platform] + build_config = {"debug": tvm_debug} + shape = (10,) dtype = "int8" @@ -153,7 +144,9 @@ def test_relay(platform, arduino_cli_cmd): with tvm.transform.PassContext(opt_level=3, config={"tir.disable_vectorize": True}): mod = tvm.relay.build(func, target=target) - with _make_session(model, arduino_board, arduino_cli_cmd, mod) as session: + with _make_session( + model, arduino_board, arduino_cli_cmd, workspace_dir, mod, build_config + ) as session: graph_mod = tvm.micro.create_local_graph_executor( mod.get_graph_json(), session.get_system_lib(), session.device ) @@ -166,9 +159,10 @@ def test_relay(platform, arduino_cli_cmd): @tvm.testing.requires_micro -def test_onnx(platform, arduino_cli_cmd): +def test_onnx(platform, arduino_cli_cmd, tvm_debug, workspace_dir): """Testing a simple ONNX model.""" - model, arduino_board = PLATFORMS[platform] + model, arduino_board = conftest.PLATFORMS[platform] + build_config = {"debug": tvm_debug} # Load test images. this_dir = os.path.dirname(__file__) @@ -191,7 +185,9 @@ def test_onnx(platform, arduino_cli_cmd): lowered = relay.build(relay_mod, target, params=params) graph = lowered.get_graph_json() - with _make_session(model, arduino_board, arduino_cli_cmd, lowered) as session: + with _make_session( + model, arduino_board, arduino_cli_cmd, workspace_dir, lowered, build_config + ) as session: graph_mod = tvm.micro.create_local_graph_executor( graph, session.get_system_lib(), session.device ) @@ -210,14 +206,26 @@ def test_onnx(platform, arduino_cli_cmd): assert np.argmax(result) == 9 -def check_result(relay_mod, model, arduino_board, arduino_cli_cmd, map_inputs, out_shape, result): +def check_result( + relay_mod, + model, + arduino_board, + arduino_cli_cmd, + workspace_dir, + map_inputs, + out_shape, + result, + build_config, +): """Helper function to verify results""" TOL = 1e-5 target = tvm.target.target.micro(model) with tvm.transform.PassContext(opt_level=3, config={"tir.disable_vectorize": True}): mod = tvm.relay.build(relay_mod, target=target) - with _make_session(model, arduino_board, arduino_cli_cmd, mod) as session: + with _make_session( + model, arduino_board, arduino_cli_cmd, workspace_dir, mod, build_config + ) as session: rt_mod = tvm.micro.create_local_graph_executor( mod.get_graph_json(), session.get_system_lib(), session.device ) @@ -237,9 +245,11 @@ def check_result(relay_mod, model, arduino_board, arduino_cli_cmd, map_inputs, o @tvm.testing.requires_micro -def test_byoc_microtvm(platform, arduino_cli_cmd): +def test_byoc_microtvm(platform, arduino_cli_cmd, tvm_debug, workspace_dir): """This is a simple test case to check BYOC capabilities of microTVM""" - model, arduino_board = PLATFORMS[platform] + model, arduino_board = conftest.PLATFORMS[platform] + build_config = {"debug": tvm_debug} + x = relay.var("x", shape=(10, 10)) w0 = relay.var("w0", shape=(10, 10)) w1 = relay.var("w1", shape=(10, 10)) @@ -291,16 +301,22 @@ def test_byoc_microtvm(platform, arduino_cli_cmd): axis=0, ), model=model, + build_config=build_config, arduino_board=arduino_board, arduino_cli_cmd=arduino_cli_cmd, + workspace_dir=workspace_dir, ) -def _make_add_sess_with_shape(model, arduino_board, arduino_cli_cmd, shape): +def _make_add_sess_with_shape( + model, arduino_board, arduino_cli_cmd, workspace_dir, shape, build_config +): A = tvm.te.placeholder(shape, dtype="int8") C = tvm.te.compute(A.shape, lambda i: A[i] + A[i], name="C") sched = tvm.te.create_schedule(C.op) - return _make_sess_from_op(model, arduino_board, arduino_cli_cmd, "add", sched, [A, C]) + return _make_sess_from_op( + model, arduino_board, arduino_cli_cmd, workspace_dir, "add", sched, [A, C], build_config + ) @pytest.mark.parametrize( @@ -312,9 +328,10 @@ def _make_add_sess_with_shape(model, arduino_board, arduino_cli_cmd, shape): ], ) @tvm.testing.requires_micro -def test_rpc_large_array(platform, arduino_cli_cmd, shape): +def test_rpc_large_array(platform, arduino_cli_cmd, tvm_debug, workspace_dir, shape): """Test large RPC array transfer.""" - model, arduino_board = PLATFORMS[platform] + 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_tensors(sess): @@ -325,7 +342,9 @@ def test_tensors(sess): C_data = tvm.nd.array(np.zeros(shape, dtype="int8"), device=sess.device) assert (C_data.numpy() == np.zeros(shape)).all() - with _make_add_sess_with_shape(model, arduino_board, arduino_cli_cmd, shape) as sess: + with _make_add_sess_with_shape( + model, arduino_board, arduino_cli_cmd, workspace_dir, shape, build_config + ) as sess: test_tensors(sess) diff --git a/tests/micro/arduino/test_arduino_workflow.py b/tests/micro/arduino/test_arduino_workflow.py index 89f146b923dc..bece4cb2be52 100644 --- a/tests/micro/arduino/test_arduino_workflow.py +++ b/tests/micro/arduino/test_arduino_workflow.py @@ -23,54 +23,38 @@ 6. Use serial connection to ensure model behaves correctly """ -PLATFORMS = conftest.PLATFORMS +# Since these tests are sequential, we'll use the same project for all tests +@pytest.fixture(scope="module") +def workspace_dir(request, platform): + return conftest.make_workspace_dir("arduino_workflow", platform) -def _generate_project(model, target, arduino_board, arduino_cli_cmd, mod, build_config): - parent_dir = os.path.dirname(__file__) - filename = os.path.splitext(os.path.basename(__file__))[0] - prev_build = ( - f"{os.path.join(parent_dir, 'archive')}_{filename}_{arduino_board}_last_build.micro" - ) - workspace_root = os.path.join( - f"{os.path.join(parent_dir, 'workspace')}_{filename}_{arduino_board}", - datetime.datetime.now().strftime("%Y-%m-%dT%H-%M-%S"), - ) - workspace_parent = os.path.dirname(workspace_root) - if not os.path.exists(workspace_parent): - os.makedirs(workspace_parent) - workspace = tvm.micro.Workspace(debug=False, root=workspace_root) - - template_project_dir = ( - pathlib.Path(__file__).parent - / ".." - / ".." - / ".." - / "apps" - / "microtvm" - / "arduino" - / "template_project" - ).resolve() - project = tvm.micro.generate_project( - str(template_project_dir), + +@pytest.fixture(scope="module") +def project_dir(workspace_dir): + return workspace_dir / "project" + + +def _generate_project(arduino_board, arduino_cli_cmd, workspace_dir, mod, build_config): + return tvm.micro.generate_project( + str(conftest.TEMPLATE_PROJECT_DIR), mod, - workspace.relpath("project"), + workspace_dir / "project", { "arduino_board": arduino_board, "arduino_cli_cmd": arduino_cli_cmd, "project_type": "example_project", - "verbose": 0, + "verbose": bool(build_config.get("debug")), }, ) - return (workspace, project) +# We MUST pass workspace_dir, not project_dir, or the workspace will be dereferenced too soon @pytest.fixture(scope="module") -def yes_no_project(platform, arduino_cli_cmd): +def project(platform, arduino_cli_cmd, tvm_debug, workspace_dir): current_dir = os.path.dirname(__file__) - model, arduino_board = PLATFORMS[platform] - # target = tvm.target.target.micro(model, options=["-link-params=1"]) - build_config = {} + model, arduino_board = conftest.PLATFORMS[platform] + build_config = {"debug": tvm_debug} with open(f"{current_dir}/testdata/yes_no.tflite", "rb") as f: tflite_model_buf = f.read() @@ -83,41 +67,29 @@ def yes_no_project(platform, arduino_cli_cmd): with tvm.transform.PassContext(opt_level=3, config={"tir.disable_vectorize": True}): mod = relay.build(mod, target, params=params) - return _generate_project(model, target, arduino_board, arduino_cli_cmd, mod, build_config) - - -@pytest.fixture(scope="module") -def project(yes_no_project): - workspace, project = yes_no_project - return project - - -@pytest.fixture(scope="module") -def project_dir(yes_no_project): - workspace, project = yes_no_project - return pathlib.Path(workspace.path) / "project" + return _generate_project(arduino_board, arduino_cli_cmd, workspace_dir, mod, build_config) -def test_project_folder_structure(project_dir): +def test_project_folder_structure(project_dir, project): assert set(["microtvm_api_server.py", "project.ino", "src"]).issubset(os.listdir(project_dir)) source_dir = project_dir / "src" assert set(os.listdir(source_dir)) == set(["model", "standalone_crt", "model.c", "model.h"]) -def test_project_model_integrity(project_dir): +def test_project_model_integrity(project_dir, project): model_dir = project_dir / "src" / "model" assert set(os.listdir(model_dir)) == set(["default_lib0.c", "default_lib1.c", "model.tar"]) -def test_model_header_templating(project_dir): +def test_model_header_templating(project_dir, project): # Ensure model.h was templated with correct WORKSPACE_SIZE with (project_dir / "src" / "model.h").open() as f: model_h = f.read() assert "#define WORKSPACE_SIZE 21312" in model_h -def test_import_rerouting(project_dir): +def test_import_rerouting(project_dir, project): # Check one file to ensure imports were rerouted runtime_path = project_dir / "src" / "standalone_crt" / "src" / "runtime" c_backend_api_path = runtime_path / "crt" / "common" / "crt_backend_api.c" diff --git a/tests/micro/arduino/testdata/mnist-8.onnx b/tests/micro/arduino/testdata/mnist-8.onnx new file mode 100644 index 000000000000..fc1a3f733c6e Binary files /dev/null and b/tests/micro/arduino/testdata/mnist-8.onnx differ diff --git a/tests/micro/zephyr/conftest.py b/tests/micro/zephyr/conftest.py index 2b30401a90e9..51ed86568871 100644 --- a/tests/micro/zephyr/conftest.py +++ b/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("-----------------") board_workspace = ( parent_dir / f"workspace_{filename}_{zephyr_board}" diff --git a/tests/scripts/task_python_microtvm.sh b/tests/scripts/task_python_microtvm.sh index aa49d90eaa43..c6e6f65ce620 100755 --- a/tests/scripts/task_python_microtvm.sh +++ b/tests/scripts/task_python_microtvm.sh @@ -25,3 +25,5 @@ source tests/scripts/setup-pytest-env.sh make cython3 run_pytest ctypes python-microtvm-zephyr tests/micro/zephyr --microtvm-platforms=host run_pytest ctypes python-microtvm-zephyr tests/micro/zephyr --microtvm-platforms=mps2_an521 +run_pytest ctypes python-microtvm-arduino-due tests/micro/arduino --microtvm-platforms=due +run_pytest ctypes python-microtvm-arduino-nano33ble tests/micro/arduino --microtvm-platforms=nano33ble