Skip to content

Commit 2252f95

Browse files
authored
[microTVM][ARM][Zephyr] Add CMSIS dependencies in Zephyr project build (#11362)
* Test with CMSIS build added disabled conv2d_nhwc_dsp.arm_cpu for non integers workloads added debugging feature to TempDirectory * revert arm_cpu strategy changes * Address Andrew comments * change copy to include * add cmsis_path only as project option
1 parent ac5d781 commit 2252f95

File tree

7 files changed

+144
-10
lines changed

7 files changed

+144
-10
lines changed

apps/microtvm/zephyr/template_project/microtvm_api_server.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,14 @@
2727
import pathlib
2828
import queue
2929
import re
30-
import select
3130
import shlex
3231
import shutil
3332
import subprocess
3433
import sys
3534
import tarfile
3635
import tempfile
3736
import threading
38-
import time
37+
from typing import Union
3938
import usb
4039

4140
import serial
@@ -323,6 +322,12 @@ def _get_nrf_device_args(options):
323322
type="str",
324323
help="Extra definitions added project compile.",
325324
),
325+
server.ProjectOption(
326+
"cmsis_path",
327+
optional=["generate_project"],
328+
type="str",
329+
help="Path to the CMSIS directory.",
330+
),
326331
]
327332

328333

@@ -333,6 +338,13 @@ def get_zephyr_base(options: dict):
333338
return zephyr_base
334339

335340

341+
def get_cmsis_path(options: dict) -> pathlib.Path:
342+
"""Returns CMSIS dependency path"""
343+
cmsis_path = options.get("cmsis_path")
344+
assert cmsis_path, "'cmsis_path' option not passed!"
345+
return pathlib.Path(cmsis_path)
346+
347+
336348
class Handler(server.ProjectAPIHandler):
337349
def __init__(self):
338350
super(Handler, self).__init__()
@@ -424,6 +436,17 @@ def _get_platform_version(self, zephyr_base: str) -> float:
424436

425437
return float(f"{version_major}.{version_minor}")
426438

439+
def _cmsis_required(self, project_path: Union[str, pathlib.Path]) -> bool:
440+
"""Check if CMSIS dependency is required."""
441+
project_path = pathlib.Path(project_path)
442+
for path in (project_path / "codegen" / "host" / "src").iterdir():
443+
if path.is_file():
444+
with open(path, "r") as lib_f:
445+
lib_content = lib_f.read()
446+
if "<arm_nnsupportfunctions.h>" in lib_content and "<arm_math.h>" in lib_content:
447+
return True
448+
return False
449+
427450
def generate_project(self, model_library_format_path, standalone_crt_dir, project_dir, options):
428451
# Check Zephyr version
429452
version = self._get_platform_version(get_zephyr_base(options))
@@ -470,8 +493,8 @@ def generate_project(self, model_library_format_path, standalone_crt_dir, projec
470493
shutil.copy2(src_path, dst_path)
471494

472495
# Populate Makefile.
473-
with open(API_SERVER_DIR / "CMakeLists.txt.template", "r") as cmake_template_f:
474-
with open(project_dir / "CMakeLists.txt", "w") as cmake_f:
496+
with open(project_dir / "CMakeLists.txt", "w") as cmake_f:
497+
with open(API_SERVER_DIR / "CMakeLists.txt.template", "r") as cmake_template_f:
475498
for line in cmake_template_f:
476499
if self.API_SERVER_CRT_LIBS_TOKEN in line:
477500
crt_libs = self.CRT_LIBS_BY_PROJECT_TYPE[options["project_type"]]
@@ -484,6 +507,20 @@ def generate_project(self, model_library_format_path, standalone_crt_dir, projec
484507
for item in flags:
485508
cmake_f.write(f"target_compile_definitions(app PUBLIC {item})\n")
486509

510+
# Include CMSIS libraries if required.
511+
if self._cmsis_required(extract_path):
512+
cmsis_path = get_cmsis_path(options)
513+
cmake_f.write("\n")
514+
cmake_f.write(
515+
f'target_include_directories(tvm_model PRIVATE {str(cmsis_path / "CMSIS" / "DSP" / "Include")})\n'
516+
)
517+
cmake_f.write(
518+
f'target_include_directories(tvm_model PRIVATE {str(cmsis_path / "CMSIS" / "DSP" / "Include" / "dsp")})\n'
519+
)
520+
cmake_f.write(
521+
f'target_include_directories(tvm_model PRIVATE {str(cmsis_path / "CMSIS" / "NN" / "Include")})\n'
522+
)
523+
487524
self._create_prj_conf(project_dir, options)
488525

489526
# Populate crt-config.h

python/tvm/contrib/utils.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,15 @@ def set_keep_for_debug(cls, set_to=True):
9393
finally:
9494
cls._KEEP_FOR_DEBUG = old_keep_for_debug
9595

96-
def __init__(self, custom_path=None):
96+
def __init__(self, custom_path=None, keep_for_debug=None):
9797
if self.TEMPDIRS is None:
9898
raise DirectoryCreatedPastAtExit()
9999

100-
self._created_with_keep_for_debug = self._KEEP_FOR_DEBUG
100+
if keep_for_debug is not None:
101+
self._created_with_keep_for_debug = keep_for_debug
102+
else:
103+
self._created_with_keep_for_debug = self._KEEP_FOR_DEBUG
104+
101105
if custom_path:
102106
os.mkdir(custom_path)
103107
self.temp_dir = custom_path
@@ -169,20 +173,22 @@ def listdir(self):
169173
atexit.register(TempDirectory.remove_tempdirs)
170174

171175

172-
def tempdir(custom_path=None):
176+
def tempdir(custom_path=None, keep_for_debug=None):
173177
"""Create temp dir which deletes the contents when exit.
174178
175179
Parameters
176180
----------
177181
custom_path : str, optional
178182
Manually specify the exact temp dir path
179183
184+
keep_for_debug : bool
185+
Keep temp directory for debugging purposes
180186
Returns
181187
-------
182188
temp : TempDirectory
183189
The temp directory object
184190
"""
185-
return TempDirectory(custom_path)
191+
return TempDirectory(custom_path=custom_path, keep_for_debug=keep_for_debug)
186192

187193

188194
class FileLock(object):

tests/micro/zephyr/conftest.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def tvm_debug(request):
5959

6060

6161
@pytest.fixture
62-
def temp_dir(board):
62+
def temp_dir(board, tvm_debug):
6363
parent_dir = pathlib.Path(os.path.dirname(__file__))
6464
filename = os.path.splitext(os.path.basename(__file__))[0]
6565
board_workspace = (
@@ -76,4 +76,21 @@ def temp_dir(board):
7676
if not os.path.exists(board_workspace.parent):
7777
os.makedirs(board_workspace.parent)
7878

79-
return tempdir(board_workspace)
79+
keep_for_debug = tvm_debug if tvm_debug else None
80+
test_temp_dir = tempdir(custom_path=board_workspace, keep_for_debug=keep_for_debug)
81+
return test_temp_dir
82+
83+
84+
@pytest.fixture(autouse=True)
85+
def skip_by_board(request, board):
86+
"""Skip test if board is in the list."""
87+
if request.node.get_closest_marker("skip_boards"):
88+
if board in request.node.get_closest_marker("skip_boards").args[0]:
89+
pytest.skip("skipped on this board: {}".format(board))
90+
91+
92+
def pytest_configure(config):
93+
config.addinivalue_line(
94+
"markers",
95+
"skip_by_board(board): skip test for the given board",
96+
)

tests/micro/zephyr/test_zephyr.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import pytest
2424
import numpy as np
25+
2526
import onnx
2627
from PIL import Image
2728

@@ -32,6 +33,7 @@
3233
from tvm.relay.testing import byoc
3334
from tvm.contrib import utils
3435
from tvm.micro.testing.utils import check_tune_log
36+
from tvm.target import arm_isa
3537

3638
import test_utils
3739

@@ -87,6 +89,7 @@ def _make_add_sess(temp_dir, model, zephyr_board, west_cmd, build_config, dtype=
8789

8890
# The same test code can be executed on both the QEMU simulation and on real hardware.
8991
@tvm.testing.requires_micro
92+
@pytest.mark.skip_boards(["mps2_an521"])
9093
def test_add_uint(temp_dir, board, west_cmd, tvm_debug):
9194
"""Test compiling the on-device runtime."""
9295

@@ -112,6 +115,7 @@ def test_basic_add(sess):
112115

113116
# The same test code can be executed on both the QEMU simulation and on real hardware.
114117
@tvm.testing.requires_micro
118+
@pytest.mark.skip_boards(["mps2_an521"])
115119
def test_add_float(temp_dir, board, west_cmd, tvm_debug):
116120
"""Test compiling the on-device runtime."""
117121
model = test_utils.ZEPHYR_BOARDS[board]
@@ -138,6 +142,7 @@ def test_basic_add(sess):
138142

139143

140144
@tvm.testing.requires_micro
145+
@pytest.mark.skip_boards(["mps2_an521"])
141146
def test_platform_timer(temp_dir, board, west_cmd, tvm_debug):
142147
"""Test compiling the on-device runtime."""
143148

@@ -167,6 +172,7 @@ def test_basic_add(sess):
167172

168173

169174
@tvm.testing.requires_micro
175+
@pytest.mark.skip_boards(["mps2_an521"])
170176
def test_relay(temp_dir, board, west_cmd, tvm_debug):
171177
"""Testing a simple relay graph"""
172178
model = test_utils.ZEPHYR_BOARDS[board]
@@ -199,6 +205,7 @@ def test_relay(temp_dir, board, west_cmd, tvm_debug):
199205

200206

201207
@tvm.testing.requires_micro
208+
@pytest.mark.skip_boards(["mps2_an521"])
202209
def test_onnx(temp_dir, board, west_cmd, tvm_debug):
203210
"""Testing a simple ONNX model."""
204211
model = test_utils.ZEPHYR_BOARDS[board]
@@ -279,6 +286,7 @@ def check_result(
279286

280287

281288
@tvm.testing.requires_micro
289+
@pytest.mark.skip_boards(["mps2_an521"])
282290
def test_byoc_microtvm(temp_dir, board, west_cmd, tvm_debug):
283291
"""This is a simple test case to check BYOC capabilities of microTVM"""
284292
model = test_utils.ZEPHYR_BOARDS[board]
@@ -359,6 +367,7 @@ def _make_add_sess_with_shape(temp_dir, model, zephyr_board, west_cmd, shape, bu
359367
],
360368
)
361369
@tvm.testing.requires_micro
370+
@pytest.mark.skip_boards(["mps2_an521"])
362371
def test_rpc_large_array(temp_dir, board, west_cmd, tvm_debug, shape):
363372
"""Test large RPC array transfer."""
364373
model = test_utils.ZEPHYR_BOARDS[board]
@@ -504,5 +513,66 @@ def test_autotune_conv2d(temp_dir, board, west_cmd, tvm_debug):
504513
tvm.testing.assert_allclose(output, expected_output, rtol=1e-4, atol=1e-5)
505514

506515

516+
@tvm.testing.requires_micro
517+
def test_schedule_build_with_cmsis_dependency(temp_dir, board, west_cmd, tvm_debug):
518+
"""Test Relay schedule with CMSIS dependency. This test shows if microTVM Auto tuning
519+
with Zephyr breaks if CMSIS dependency was required for a schedule.
520+
"""
521+
model = test_utils.ZEPHYR_BOARDS[board]
522+
build_config = {"debug": tvm_debug}
523+
target = tvm.target.target.micro(model, options=["-keys=arm_cpu,cpu"])
524+
525+
isa = arm_isa.IsaAnalyzer(target)
526+
if not isa.has_dsp_support:
527+
pytest.skip(f"ISA does not support DSP. target: {target}")
528+
529+
# Create a Relay conv2d
530+
data_shape = (1, 16, 16, 3)
531+
weight_shape = (5, 5, 8, 3)
532+
data = relay.var("data", relay.TensorType(data_shape, "int8"))
533+
weight = relay.var("weight", relay.TensorType(weight_shape, "int8"))
534+
y = relay.nn.conv2d(
535+
data,
536+
weight,
537+
padding=(2, 2),
538+
kernel_size=(5, 5),
539+
data_layout="NHWC",
540+
kernel_layout="HWOI",
541+
out_dtype="int32",
542+
)
543+
func = relay.Function([data, weight], y)
544+
ir_mod = tvm.IRModule.from_expr(func)
545+
546+
runtime = Runtime("crt", {"system-lib": True})
547+
548+
with tvm.transform.PassContext(opt_level=3, config={"tir.disable_vectorize": True}):
549+
mod = tvm.relay.build(ir_mod, target=target, runtime=runtime)
550+
551+
project_options = {
552+
"project_type": "host_driven",
553+
"west_cmd": west_cmd,
554+
"verbose": bool(build_config.get("debug")),
555+
"zephyr_board": board,
556+
"cmsis_path": os.getenv("CMSIS_PATH"),
557+
}
558+
559+
project_dir = temp_dir / "project"
560+
project = tvm.micro.generate_project(
561+
str(test_utils.TEMPLATE_PROJECT_DIR),
562+
mod,
563+
project_dir,
564+
project_options,
565+
)
566+
project.build()
567+
568+
with open(project_dir / "CMakeLists.txt", "r") as cmake_f:
569+
cmake_content = cmake_f.read()
570+
571+
assert "CMSIS/DSP/Include" in cmake_content
572+
assert "CMSIS/DSP/Include/dsp" in cmake_content
573+
assert "CMSIS/DSP/Include" in cmake_content
574+
assert "CMSIS/NN/Include" in cmake_content
575+
576+
507577
if __name__ == "__main__":
508578
tvm.testing.main()

tests/micro/zephyr/test_zephyr_aot.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838

3939

4040
@tvm.testing.requires_micro
41+
@pytest.mark.skip_boards(["mps2_an521"])
4142
def test_tflite(temp_dir, board, west_cmd, tvm_debug):
4243
"""Testing a TFLite model."""
4344
model = test_utils.ZEPHYR_BOARDS[board]
@@ -93,6 +94,7 @@ def test_tflite(temp_dir, board, west_cmd, tvm_debug):
9394

9495

9596
@tvm.testing.requires_micro
97+
@pytest.mark.skip_boards(["mps2_an521"])
9698
def test_qemu_make_fail(temp_dir, board, west_cmd, tvm_debug):
9799
"""Testing QEMU make fail."""
98100
if board not in ["qemu_x86", "mps2_an521", "mps3_an547"]:

tests/micro/zephyr/test_zephyr_armv7m.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ def _apply_desired_layout_no_simd(relay_mod):
103103

104104

105105
@tvm.testing.requires_micro
106+
@pytest.mark.skip_boards(["mps2_an521"])
106107
def test_armv7m_intrinsic(temp_dir, board, west_cmd, tvm_debug):
107108
"""Testing a ARM v7m SIMD extension."""
108109

tests/scripts/task_python_microtvm.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ make cython3
2727
run_pytest ctypes python-microtvm-zephyr-qemu_x86 tests/micro/zephyr --zephyr-board=qemu_x86
2828
run_pytest ctypes python-microtvm-zephyr-qemu_riscv32 tests/micro/zephyr --zephyr-board=qemu_riscv32
2929
run_pytest ctypes python-microtvm-zephyr-qemu_riscv64 tests/micro/zephyr --zephyr-board=qemu_riscv64
30+
run_pytest ctypes python-microtvm-zephyr-mps2_an521 tests/micro/zephyr --zephyr-board=mps2_an521
3031

3132
# Arduino
3233
run_pytest ctypes python-microtvm-arduino apps/microtvm/arduino/template_project/tests

0 commit comments

Comments
 (0)