aboutsummaryrefslogtreecommitdiff
path: root/driver_library
diff options
context:
space:
mode:
authorKshitij Sisodia <kshitij.sisodia@arm.com>2022-09-30 16:42:50 +0100
committerKristofer Jonsson <kristofer.jonsson@arm.com>2022-10-25 16:36:45 +0000
commitf9efe0ddf865c55d28bcaa203fefffa94bf09b42 (patch)
tree928da5f68dafd7e5a0dee05a75f2f6fcd52df170 /driver_library
parent569aa558f5e7638852a928feede1f21e7323f664 (diff)
downloadethos-u-linux-driver-stack-f9efe0ddf865c55d28bcaa203fefffa94bf09b42.tar.gz
Added Python interface for Arm Ethos-U NPU driver library.22.11-rc1
Python `ethosu_driver` could be built as part of Arm Ethos-U Linux driver library CMake flow. See driver_library/python/README.md for more details. Change-Id: I177a890add5c13df9a839f4f43621f972afe5ab1 Signed-off-by: Kshitij Sisodia <kshitij.sisodia@arm.com>
Diffstat (limited to 'driver_library')
-rw-r--r--driver_library/CMakeLists.txt11
-rw-r--r--driver_library/include/ethosu.hpp2
-rw-r--r--driver_library/python/CMakeLists.txt75
-rw-r--r--driver_library/python/MANIFEST.in1
-rw-r--r--driver_library/python/README.md336
-rwxr-xr-xdriver_library/python/init_devenv.sh10
-rw-r--r--driver_library/python/pylintconfig427
-rw-r--r--driver_library/python/setup.py204
-rw-r--r--driver_library/python/src/ethosu_driver/__init__.py6
-rw-r--r--driver_library/python/src/ethosu_driver/_generated/__init__.py2
-rw-r--r--driver_library/python/src/ethosu_driver/_utilities/__init__.py5
-rw-r--r--driver_library/python/src/ethosu_driver/_utilities/driver_utilities.py186
-rw-r--r--driver_library/python/src/ethosu_driver/inference_runner.py100
-rw-r--r--driver_library/python/src/ethosu_driver/swig/driver.i554
-rw-r--r--driver_library/python/src/ethosu_driver/swig/standard_header.i50
-rw-r--r--driver_library/python/src/ethosu_driver/swig/typemaps/buffer.i42
-rwxr-xr-xdriver_library/python/swig_generate.py27
-rw-r--r--driver_library/python/test/conftest.py34
-rw-r--r--driver_library/python/test/test_capabilities.py73
-rw-r--r--driver_library/python/test/test_driver.py179
-rw-r--r--driver_library/python/test/test_driver_utilities.py77
-rw-r--r--driver_library/python/test/test_inference.py50
-rw-r--r--driver_library/python/test/test_shadow_classes.py20
-rw-r--r--driver_library/python/test/testdata/download.py46
-rw-r--r--driver_library/src/ethosu.cpp2
-rw-r--r--driver_library/src/ethosu_stub.cpp2
26 files changed, 2517 insertions, 4 deletions
diff --git a/driver_library/CMakeLists.txt b/driver_library/CMakeLists.txt
index 375192f..4c247ae 100644
--- a/driver_library/CMakeLists.txt
+++ b/driver_library/CMakeLists.txt
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2020,2022 Arm Limited.
+# SPDX-FileCopyrightText: Copyright 2020, 2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
#
# SPDX-License-Identifier: Apache-2.0
#
@@ -18,6 +18,9 @@
cmake_minimum_required(VERSION 3.0.2)
+option(BUILD_PYTHON_WHL "Build Python wheel package" OFF)
+option(BUILD_PYTHON_SRC "Build Python source package" OFF)
+
# set the project name and version
project("driver_library" VERSION 1.0.0 LANGUAGES C CXX)
@@ -28,9 +31,15 @@ add_library(ethosu STATIC "src/ethosu.cpp")
target_include_directories(ethosu PUBLIC "include")
set_target_properties(ethosu PROPERTIES PUBLIC_HEADER "include/ethosu.hpp")
set_target_properties(ethosu PROPERTIES VERSION ${PROJECT_VERSION})
+set_target_properties(ethosu PROPERTIES POSITION_INDEPENDENT_CODE ON)
# Install library and public headers
install(TARGETS ethosu
LIBRARY DESTINATION "lib"
ARCHIVE DESTINATION "lib"
PUBLIC_HEADER DESTINATION "include")
+
+## Build Python bindings
+if (BUILD_PYTHON_WHL OR BUILD_PYTHON_SRC)
+ add_subdirectory(python)
+endif()
diff --git a/driver_library/include/ethosu.hpp b/driver_library/include/ethosu.hpp
index 74f8abb..15957f4 100644
--- a/driver_library/include/ethosu.hpp
+++ b/driver_library/include/ethosu.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020-2022 Arm Limited.
+ * SPDX-FileCopyrightText: Copyright 2020-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
*
* SPDX-License-Identifier: Apache-2.0
*
diff --git a/driver_library/python/CMakeLists.txt b/driver_library/python/CMakeLists.txt
new file mode 100644
index 0000000..5603273
--- /dev/null
+++ b/driver_library/python/CMakeLists.txt
@@ -0,0 +1,75 @@
+#
+# SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the License); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an AS IS BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+find_package(PythonInterp 3.5 REQUIRED)
+if(NOT ${PYTHONINTERP_FOUND})
+ message(FATAL_ERROR "Python 3.5 or greater is required to build python driver, but was not found")
+endif()
+
+find_package(SWIG 3.0.12 REQUIRED)
+if(NOT ${SWIG_FOUND})
+ message(FATAL_ERROR "SWIG 3.0.12 or greater is required to build python driver, but was not found")
+endif()
+
+set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py")
+set(SWIG_GENERATE "${CMAKE_CURRENT_BINARY_DIR}/swig_generate.py")
+set(OUT_WRAP "${CMAKE_CURRENT_BINARY_DIR}/pydriver.wrap.timestamp")
+
+# local env variables passed down to the python scripts
+# scripts can thus be used standalone
+set(DRIVER_ENV ETHOS_U_DRIVER_INCLUDE="${PROJECT_SOURCE_DIR}/include"
+ ETHOS_U_DRIVER_LIB=${PROJECT_BINARY_DIR}/lib)
+
+# common step - generates swig wrappers
+add_custom_command(OUTPUT ${OUT_WRAP}
+ COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/README.md ${CMAKE_CURRENT_BINARY_DIR}
+ COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE ${CMAKE_CURRENT_BINARY_DIR}
+ COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/swig_generate.py ${CMAKE_CURRENT_BINARY_DIR}
+ COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/setup.py ${CMAKE_CURRENT_BINARY_DIR}
+ COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_BINARY_DIR}/src
+ COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --green "Clearing Python build ..."
+ COMMAND ${PYTHON_EXECUTABLE} ${SETUP_PY} --quiet clean --all
+ COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --green "Generating SWIG wrappers ..."
+ COMMAND ${PYTHON_EXECUTABLE} ${SWIG_GENERATE}
+ DEPENDS ethosu)
+
+# source package
+if(BUILD_PYTHON_SRC)
+ set(OUT_SRC "${CMAKE_CURRENT_BINARY_DIR}/pydriver.src.timestamp")
+ add_custom_command(OUTPUT ${OUT_SRC}
+ COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --green "Building Python source package ..."
+ COMMAND ${PYTHON_EXECUTABLE} ${SETUP_PY} sdist
+ COMMAND ${CMAKE_COMMAND} -E touch ${OUT_SRC}
+ DEPENDS ${OUT_WRAP})
+endif()
+# wheel package
+if(BUILD_PYTHON_WHL)
+
+ find_package(PythonLibs 3.5 REQUIRED)
+ if(NOT ${PYTHONLIBS_FOUND})
+ message(FATAL_ERROR "Python 3.5 or greater development libraries were not found.")
+ endif()
+
+ set(OUT_WHL "${CMAKE_CURRENT_BINARY_DIR}/pydriver.whl.timestamp")
+ add_custom_command(OUTPUT ${OUT_WHL}
+ COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --green "Building Python binary package ..."
+ COMMAND ${CMAKE_COMMAND} -E env ${DRIVER_ENV} CXX=${CMAKE_CXX_COMPILER} CC=${CMAKE_C_COMPILER} ${PYTHON_EXECUTABLE} ${SETUP_PY} bdist_wheel
+ COMMAND ${CMAKE_COMMAND} -E touch ${OUT_WHL}
+ DEPENDS ${OUT_WRAP})
+endif()
+add_custom_target(pydriver ALL DEPENDS ${OUT_WRAP} ${OUT_SRC} ${OUT_WHL})
diff --git a/driver_library/python/MANIFEST.in b/driver_library/python/MANIFEST.in
new file mode 100644
index 0000000..6356387
--- /dev/null
+++ b/driver_library/python/MANIFEST.in
@@ -0,0 +1 @@
+prune test
diff --git a/driver_library/python/README.md b/driver_library/python/README.md
new file mode 100644
index 0000000..7fae749
--- /dev/null
+++ b/driver_library/python/README.md
@@ -0,0 +1,336 @@
+# About Python ethosu_driver
+
+Python ethosu_driver is an extension for
+[Arm Ethos-U driver library](https://review.mlplatform.org/plugins/gitiles/ml/ethos-u/ethos-u-linux-driver-stack/).
+Python ethosu_driver provides interface similar to Ethos-u Linux driver C++
+Api.
+
+The Python package is built with public headers from the
+[driver_library/include](https://review.mlplatform.org/plugins/gitiles/ml/ethos-u/ethos-u-linux-driver-stack/+/refs/heads/master/driver_library/include/)
+folder.
+
+The [SWIG](http://www.swig.org/) tool is used to generate the Ethos-U driver
+library Python shadow classes and C wrapper.
+
+## Python ethosu_driver library installation
+
+Python ethosu_driver library can be packaged as a source package or a binary
+package (wheel). Binary package is platform dependent, the name of the
+package will indicate the platform it was built for, e.g.:
+
+* Linux Aarch 64 bit machine: ethosu_driver-1.0.0-cp37-cp37m-linux_aarch64.whl
+
+The source package is platform independent but installation will involve
+Ethos-U driver library C wrapper compilation on a target machine.
+You will need to have g++ compatible with C++ 14 standard and a Python
+development library installed on the target machine.
+
+Python driver binary package is linked statically with C++ Ethos-U driver
+library and can operate independently from it when built.
+Python driver source package requires static Ethos-U driver library -
+libethosu.a - and public header during installation, thus they must be
+present on the target machine.
+
+### Installing from wheel
+
+Install ethosu_driver from binary by pointing to the wheel file:
+
+1) If corresponding wheel is available for your platform architecture in the
+public repository.
+ ```
+ pip install ethosu_driver
+ ```
+2) If you have local wheel file. Example:
+ ```
+ pip install /path/to/ethosu_driver-X.X.X-cp37-cp37m-linux_aarch64.whl
+ ```
+
+### Installing from source package
+
+While installing from sources, you can choose Ethos-U driver library to
+be used. By default, library will be searched in standard for your system
+locations. You can check them by running:
+```
+gcc --print-search-dirs
+```
+Headers will be searched in standard include directories for your system.
+If Ethos-U driver library has custom location, set environment variables
+*ETHOS_U_DRIVER_LIB* and *ETHOS_U_DRIVER_INCLUDE* to point to Ethos-U driver
+library (libethosu.a) and header (ethosu.hpp):
+```
+export ETHOS_U_DRIVER_LIB=/path/to/lib
+export ETHOS_U_DRIVER_INCLUDE=/path/to/headers
+```
+
+Installing from the public repository.
+```
+pip install ethosu_driver
+```
+Installing from local file.
+```
+pip install /path/to/ethosu_driver-X.X.X.tar.gz
+```
+
+If ethosu_driver installation script fails to find Ethos-U driver libraries it
+will raise an error like this
+
+`RuntimeError: Ethos-U driver library was not found in
+('/usr/lib/gcc/aarch64-linux-gnu/8/', <...> ,'/lib/', '/usr/lib/').
+Please install driver to one of the standard locations or set correct
+ETHOS_U_DRIVER_INCLUDE and ETHOS_U_DRIVER_LIB env variables.`
+
+You can now verify that ethosu_driver library is installed and check
+ethosu_driver version using:
+
+```
+pip show ethosu_driver
+```
+
+## Building Python ethosu_driver library locally
+
+### Install SWIG
+
+We suggest to use SWIG version 3.0.12 or newer. You can check available swig
+version for you system here: https://pkgs.org/download/swig.
+
+For example, install the tool with Ubuntu package manager as follows:
+```
+sudo apt install swig
+```
+If your system has swig version less than 3.0.12, please, build and install
+from sources:
+
+1. Download SWIG:
+ ```
+ wget https://github.com/swig/swig/archive/refs/tags/v4.0.2.zip
+ unzip v4.0.2.zip
+ ```
+2. Build and install SWIG:
+ ```
+ cd swig-4.0.2
+ ./autogen.sh
+ ./configure --prefix=<set your system installation prefix>
+ make
+ make install
+ ```
+
+### Building as part of cmake flow
+
+To build Python ethosu_driver as part of Ethos-U NPU Linux driver stack provide
+the following cmake flags:
+1) For source distribution
+ ```
+ -DBUILD_PYTHON_SRC=1
+ ```
+2) For wheel distribution
+ ```
+ -DBUILD_PYTHON_WHL=1
+ ```
+Note: you will need to have a Python instance for your target platform to build
+wheel. For example, if you are building for an AArch64 platform, you will need
+Python installation for aarch64-linux-gnu tool-chain.
+
+Build result can be found in `<your cmake build dir>/python/dist`.
+
+### Building standalone
+
+Navigate to `driver_libarary/python` and execute:
+```
+python3 setup.py clean --all
+python3 ./swig_generate.py
+python3 setup.py sdist
+```
+Build result can be found in `driver_libarary/python/dist`.
+
+## Python ethosu_driver API overview
+
+### Getting started
+
+After the Python driver library is installed with pip and can be accessed
+within your work environment, import it in your script:
+
+```python
+import ethosu_driver as driver
+```
+
+Create a device. You can ping Ethos-U device with `ping` method:
+
+```python
+device = driver.Device("/dev/ethosu0")
+device.ping()
+```
+
+You can create memory buffer with data from a binary file or Python buffer
+object:
+
+```python
+# from file:
+network_file = "/path/to/model.tflite"
+network_buffer = driver.Buffer(device, network_file)
+
+# from numpy:
+ifm_zeros = numpy.zeros(ifm_size, dtype=np.uint8)
+ifm_buffer = driver.Buffer(device, ifm_size)
+ifm_buffer.from_buffer(ifm_zeros.data)
+```
+
+To create a network object, provide memory buffer for the model file and
+created device:
+
+```python
+network = driver.Network(device, network_buffer)
+```
+
+Inference object is instantiated with a network object and lists of input
+memory buffers and output memory buffers.
+For example:
+
+```python
+ifms = [ifm_buffer]
+
+ofms = []
+for ofm_size in network.getOfmDims():
+ ofm_buffer = driver.Buffer(device, ofm_size)
+ ofms.append(ofm_buffer)
+
+inference = driver.Inference(network, ifms, ofms)
+```
+
+To execute the inference and wait for the callback:
+
+```python
+# wait infinitely
+inference.wait()
+
+# wait with a timeout in nano seconds:
+inference.wait(timeoutNanos=60e9)
+```
+
+To read results of the inference, iterate through available outputs and convert
+them to numpy array:
+
+```python
+for buffer_out in inference.getOfmBuffers():
+array = np.frombuffer(buffer_out.data(), dtype=np.uint8)
+```
+
+See inline py docs for more info on driver public API.
+
+## Inference runner
+
+Python ethosu_driver library comes with a script `inference_runner` that is
+installed to the Python environment `bin` directory and could be invoked
+from a command line by the file name:
+
+```cmd
+inference_runner <args>
+```
+
+Arguments:
+
+* `--device` : Npu device name. Default: ethosu0.
+* `--model` : Tflite model file path.
+* `--timeout` : inference timeout in seconds, Default: infinite.
+* `--inputs` : list of files containing input feature maps.
+* `--output_dir` : directory to store inference results, output feature maps.
+Default: current directory.
+* `--npy` : Use npy input/output. Default: 0.
+* `--profile_counters`: profile counters to measure during inference, accepts
+four integers chosen from
+[HW Supported Ethos-U PMU
+Events](https://review.mlplatform.org/plugins/gitiles/ml/ethos-u/ethos-u-core-driver/+/refs/heads/master/include/pmu_ethosu.h).
+
+Example:
+
+```cmd
+inference_runner --device ethosu0 --model ./mobilenet_v2_vela.tflite --timeout
+60 --npy 1 --inputs ./input1.npy --output_dir ./ofms
+```
+
+## Using inference runner with numpy
+
+Python ethosu_driver libarary could be installed with numpy support.
+Numpy will be automatically downloaded and installed alongside ethosu_driver.
+If your machine does not have access to pypi repositories you might need to
+install NumPy in advance by following public instructions:
+<https://scipy.org/install.html>, or to have it installed from wheel package
+built for your platform.
+
+Now, if you provide inference runner command line parameter `--npy 1` the
+script will interpret input files as numpy array exports and will save output
+feature maps as numpy arrays.
+
+## Setup development environment
+
+Before, proceeding to the next steps, make sure that:
+
+1. You have Python 3.6+ installed system-side. The package is not compatible
+with older Python versions.
+2. You have python3.6-dev installed system-side. This contains header files
+needed to build ethosu_driver extension module.
+3. In case you build Python from sources manually, make sure that the following
+libraries are installed and available in you system:
+``python3.6-dev build-essential checkinstall libreadline-gplv2-dev
+libncursesw5-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev
+libbz2-dev``
+4. install SWIG, swig must be version 4.*
+
+## Setup virtual environment
+
+Now you can proceed with setting up workspace:
+
+1. Set environment variables ETHOS_U_DRIVER_LIB (pointing to Ethos-U driver
+library) and ETHOS_U_DRIVER_INCLUDE (pointing to Ethos-U driver library
+headers)
+2. Create development env using script ``init_devenv.sh``
+
+## Generating SWIG wrappers
+
+Before building package or running tests you need to generate SWIG wrappers
+based on the interface [files](src/ethosu_driver/swig).
+```commandline
+python setup.py clean --all
+python ./swig_generate.py
+```
+
+## Running unit-tests
+
+Tests could be executed only on a system with Ethos-U NPU device.
+Pytest is used as unit-test framework, before running the test you need to
+install pytest and numpy or include into your rootfs image:
+
+```commandline
+pip install pytest
+pip install numpy
+```
+
+Execute command from the project root dir:
+```
+pytest -v
+```
+
+## Regenerate SWIG stubs inplace
+
+If you want to check that swig wrappers are compiling correctly, you can issue
+extension compilation inplace:
+
+1) clean old generated files:
+
+ ```bash
+ python setup.py clean --all
+ ```
+
+2) Run swig wrapper source generation as described in [Generating SWIG
+wrappers](#generating-swig-wrappers)
+3) Run `build_ext` command:
+
+ ```bash
+ export ETHOS_U_DRIVER_LIB=/path/to/driver/lib
+ export ETHOS_U_DRIVER_INCLUDE=/path/to/driver/include
+ python setup.py build_ext --inplace
+ ```
+
+It will put all generated files under ./src/ethosu_driver/_generated folder.
+Command will fail on x86 machine during linkage phase because Ethos-U driver
+is built for Arm platform, but compilation stage should pass successfully
+(or give you indication of compilation problems).
diff --git a/driver_library/python/init_devenv.sh b/driver_library/python/init_devenv.sh
new file mode 100755
index 0000000..53544e7
--- /dev/null
+++ b/driver_library/python/init_devenv.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+# SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-License-Identifier: Apache-2.0
+
+set -e
+SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
+
+python3 -m venv devenv
+source devenv/bin/activate
+python "${SCRIPT_DIR}"/test/testdata/download.py
diff --git a/driver_library/python/pylintconfig b/driver_library/python/pylintconfig
new file mode 100644
index 0000000..df550a2
--- /dev/null
+++ b/driver_library/python/pylintconfig
@@ -0,0 +1,427 @@
+# SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-License-Identifier: Apache-2.0
+[MASTER]
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code
+extension-pkg-whitelist=
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS,generated,_generated
+
+# Add files or directories matching the regex patterns to the blacklist. The
+# regex matches against base names, not paths.
+ignore-patterns=_version.py
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Use multiple processes to speed up Pylint.
+jobs=1
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# Specify a configuration file.
+#rcfile=
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+unsafe-load-any-extension=no
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
+confidence=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once).You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use"--disable=all --enable=classes
+# --disable=W"
+disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once). See also the "--disable" option for examples.
+enable=
+
+
+[REPORTS]
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details
+#msg-template=
+
+# Set the output format. Available formats are text, parseable, colorized, json
+# and msvs (visual studio).You can also give a reporter class, eg
+# mypackage.mymodule.MyReporterClass.
+output-format=colorized
+
+# Tells whether to display a full report or only the messages
+reports=yes
+
+# Activate the evaluation score.
+score=yes
+
+
+[REFACTORING]
+
+# Maximum number of nested blocks for function / method body
+max-nested-blocks=5
+
+
+[BASIC]
+
+# Naming hint for argument names
+argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
+
+# Regular expression matching correct argument names
+argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
+
+# Naming hint for attribute names
+attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
+
+# Regular expression matching correct attribute names
+attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# Naming hint for class attribute names
+class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Regular expression matching correct class attribute names
+class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Naming hint for class names
+class-name-hint=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression matching correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Naming hint for constant names
+const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression matching correct constant names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
+
+# Naming hint for function names
+function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
+
+# Regular expression matching correct function names
+function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Include a hint for the correct naming format with invalid-name
+include-naming-hint=no
+
+# Naming hint for inline iteration names
+inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
+
+# Regular expression matching correct inline iteration names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Naming hint for method names
+method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
+
+# Regular expression matching correct method names
+method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
+
+# Naming hint for module names
+module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression matching correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=^_
+
+# List of decorators that produce properties, such as abc.abstractproperty. Add
+# to this list to register other decorators that produce valid properties.
+property-classes=abc.abstractproperty
+
+# Naming hint for variable names
+variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
+
+# Regular expression matching correct variable names
+variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
+
+
+[SPELLING]
+
+# Spelling dictionary name. Available dictionaries: none. To make it working
+# install python-enchant package.
+spelling-dict=
+
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
+
+# A path to a file that contains private dictionary; one word per line.
+spelling-private-dict-file=
+
+# Tells whether to store unknown words to indicated private dictionary in
+# --spelling-private-dict-file option instead of raising a message.
+spelling-store-unknown-words=no
+
+
+[SIMILARITIES]
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=no
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+
+[FORMAT]
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+
+# Number of spaces of indent required inside a hanging or continued line.
+indent-after-paren=4
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+# Maximum number of characters on a single line.
+max-line-length=120
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# List of optional constructs for which whitespace checking is disabled. `dict-
+# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
+# `trailing-comma` allows a space between comma and closing bracket: (a, ).
+# `empty-line` allows space-only lines.
+no-space-check=trailing-comma,dict-separator
+
+# Allow the body of a class to be on the same line as the declaration if body
+# contains single statement.
+single-line-class-stmt=no
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+
+[LOGGING]
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format
+logging-modules=logging
+
+
+[TYPECHECK]
+
+# List of decorators that produce context managers, such as
+# contextlib.contextmanager. Add to this list to register other decorators that
+# produce valid context managers.
+contextmanager-decorators=contextlib.contextmanager
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# This flag controls whether pylint should warn about no-member and similar
+# checks whenever an opaque object is returned when inferring. The inference
+# can return multiple potential results while evaluating a Python object, but
+# some branches might not be evaluated, which results in partial inference. In
+# that case, it might be useful to still emit no-member and other checks for
+# the rest of the inferred objects.
+ignore-on-opaque-inference=yes
+
+# List of class names for which member attributes should not be checked (useful
+# for classes with dynamically set attributes). This supports the use of
+# qualified names.
+ignored-classes=optparse.Values,thread._local,_thread._local
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis. It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=
+
+# Show a hint with possible names when a member name was not found. The aspect
+# of finding the hint is based on edit distance.
+missing-member-hint=yes
+
+# The minimum edit distance a name should have in order to be considered a
+# similar match for a missing member name.
+missing-member-hint-distance=1
+
+# The total number of similar names that should be taken in consideration when
+# showing a hint for a missing member.
+missing-member-max-choices=1
+
+
+[VARIABLES]
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+# Tells whether unused global variables should be treated as a violation.
+allow-global-unused-variables=yes
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,_cb
+
+# A regular expression matching the name of dummy variables (i.e. expectedly
+# not used).
+dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*|^ignored_|^unused_
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# List of qualified module names which can have objects that can redefine
+# builtins.
+redefining-builtins-modules=six.moves,future.builtins
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+[IMPORTS]
+
+# Allow wildcard imports from modules that define __all__.
+allow-wildcard-with-all=no
+
+# Analyse import fallback blocks. This can be used to support both Python 2 and
+# 3 compatible code, which means that the block might have code that exists
+# only in one or another interpreter, leading to false positives when analysed.
+analyse-fallback-blocks=no
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=optparse,tkinter.tix
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+# Force import order to recognize a module as part of the standard
+# compatibility libraries.
+known-standard-library=
+
+# Force import order to recognize a module as part of a third party library.
+known-third-party=enchant
+
+
+[CLASSES]
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,_fields,_replace,_source,_make
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=mcs
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=5
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Maximum number of boolean expressions in a if statement
+max-bool-expr=5
+
+# Maximum number of branch for function / method body
+max-branches=12
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception
diff --git a/driver_library/python/setup.py b/driver_library/python/setup.py
new file mode 100644
index 0000000..f90cd26
--- /dev/null
+++ b/driver_library/python/setup.py
@@ -0,0 +1,204 @@
+#
+# SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-License-Identifier: Apache-2.0
+#
+import logging
+import os
+from functools import lru_cache
+from itertools import chain
+from pathlib import Path
+
+from setuptools import setup
+from distutils.core import Extension
+from setuptools.command.build_py import build_py
+
+logger = logging.Logger(__name__)
+
+
+def linux_gcc_lib_search():
+ """
+ Calls the `gcc` to get linker default system paths.
+ Returns:
+ list of paths
+ """
+ cmd = 'gcc --print-search-dirs | grep libraries'
+ cmd_res = os.popen(cmd).read()
+ cmd_res = cmd_res.split('=')
+ if len(cmd_res) > 1:
+ return tuple(cmd_res[1].split(':'))
+ return None
+
+
+def find_includes(include_env: str = 'ETHOS_U_DRIVER_INCLUDE'):
+ include_path = os.getenv(include_env, '')
+ return [include_path] if include_path else ['/usr/local/include', '/usr/include']
+
+
+@lru_cache(maxsize=1)
+def find_driver(lib_name: str,
+ optional: bool = False,
+ libs_env: str = 'ETHOS_U_DRIVER_LIB',
+ default_lib_search: tuple = linux_gcc_lib_search()):
+ """
+ Searches for library installation on the local machine.
+
+ Args:
+ lib_name: library name to find
+ optional: Do not fail if optional. Default is False - fail if library was not found.
+ libs_env: custom environment variable pointing to libraries location, default is 'ETHOS_U_DRIVER_LIB'
+ default_lib_search: list of paths to search for a library if not found within path provided by
+ 'ETHOS_U_DRIVER_LIB' env variable
+
+ Returns:
+ tuple containing name of the driver libs, paths to the libs
+ """
+
+ lib_path = os.getenv(libs_env, "")
+
+ lib_search = [lib_path] if lib_path else default_lib_search
+
+ libs = dict(map(lambda path: (':{}'.format(path.name), path),
+ chain.from_iterable(map(lambda lib_path: Path(lib_path).glob(lib_name),
+ lib_search))))
+ if not optional and len(libs) == 0:
+ raise RuntimeError("""Ethos-U driver library {} was not found in {}. Please install driver to one of the standard
+ locations or set correct ETHOS_U_DRIVER_INCLUDE and ETHOS_U_DRIVER_LIB env variables."""
+ .format(lib_name, lib_search))
+
+ # gives back tuple of names of the libs, set of unique libs locations and includes.
+ return list(libs.keys()), list(set(
+ map(lambda path: str(path.absolute().parent), libs.values())))
+
+
+class LibFinderExtension(Extension):
+ """
+ Derived from `Extension` this class adds libraries search on the user's machine.
+ SWIG options and compilation flags are updated with relevant libraries files locations (-L) and headers (-I).
+
+ Search for the library is executed only when attributes include_dirs, library_dirs, runtime_library_dirs, libraries or
+ swig_opts are queried.
+
+ """
+
+ def __init__(self, name, sources, libs, include_dirs=None, define_macros=None, undef_macros=None,
+ library_dirs=None,
+ libraries=None, runtime_library_dirs=None, extra_objects=None, extra_compile_args=None,
+ extra_link_args=None, export_symbols=None, language=None, **kw):
+ self._include_dirs = None
+ self._library_dirs = None
+ self._runtime_library_dirs = None
+ self._libs = libs
+ super().__init__(name, sources, include_dirs, define_macros, undef_macros, library_dirs, libraries,
+ runtime_library_dirs, extra_objects, extra_compile_args, extra_link_args, export_symbols,
+ language, **kw)
+
+ @property
+ def include_dirs(self):
+ return self._include_dirs + find_includes()
+
+ @include_dirs.setter
+ def include_dirs(self, include_dirs):
+ self._include_dirs = include_dirs
+
+ @property
+ def library_dirs(self):
+ library_dirs = self._library_dirs
+ for lib in self._libs:
+ _, lib_path = find_driver(lib)
+ library_dirs = library_dirs + lib_path
+
+ return library_dirs
+
+ @library_dirs.setter
+ def library_dirs(self, library_dirs):
+ self._library_dirs = library_dirs
+
+ @property
+ def runtime_library_dirs(self):
+ library_dirs = self._runtime_library_dirs
+ for lib in self._libs:
+ _, lib_path = find_driver(lib)
+ library_dirs = library_dirs + lib_path
+
+ return library_dirs
+
+ @runtime_library_dirs.setter
+ def runtime_library_dirs(self, runtime_library_dirs):
+ self._runtime_library_dirs = runtime_library_dirs
+
+ @property
+ def libraries(self):
+ libraries = self._libraries
+ for lib in self._libs:
+ lib_names, _ = find_driver(lib)
+ libraries = libraries + lib_names
+
+ return libraries
+
+ @libraries.setter
+ def libraries(self, libraries):
+ self._libraries = libraries
+
+ def __eq__(self, other):
+ return self.__class__ == other.__class__ and self.name == other.name
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __hash__(self):
+ return self.name.__hash__()
+
+
+class ExtensionPriorityBuilder(build_py):
+ """
+ Runs extension builder before other stages. Otherwise generated files are not included to the distribution.
+ """
+
+ def run(self):
+ self.run_command('build_ext')
+ return super().run()
+
+
+if __name__ == '__main__':
+ # mandatory extensions
+ driver_module = LibFinderExtension('ethosu_driver._generated._driver',
+ sources=['src/ethosu_driver/_generated/driver_wrap.cpp'],
+ extra_compile_args=['-std=gnu++14'],
+ language='c++',
+ libs=['libethosu.a']
+ )
+
+ extensions_to_build = [driver_module]
+
+ setup(
+ name='ethosu_driver',
+ version='1.0.0',
+ author='Arm ltd',
+ author_email='support@arm.com',
+ description='Arm Ethos-U NPU Linux Driver Stack Python wrapper',
+ url='https://git.mlplatform.org/ml/ethos-u/ethos-u-linux-driver-stack.git/',
+ license='Apache License 2.0',
+ classifiers=[
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: Apache Software License",
+ "Operating System :: POSIX :: Linux",
+ "Programming Language :: C",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.5",
+ "Topic :: Scientific/Engineering :: Artificial Intelligence"
+ ],
+ keywords=["ethos-u", "driver", "npu"],
+ package_dir={'': 'src'},
+ packages=[
+ 'ethosu_driver',
+ 'ethosu_driver._generated',
+ 'ethosu_driver._utilities'
+ ],
+ data_files=[('', ['LICENSE'])],
+ entry_points={"console_scripts": ["inference_runner = ethosu_driver.inference_runner:main"]},
+ python_requires='>=3.5',
+ extras_require={"numpy": ["numpy"]},
+ cmdclass={'build_py': ExtensionPriorityBuilder},
+ ext_modules=extensions_to_build
+ )
diff --git a/driver_library/python/src/ethosu_driver/__init__.py b/driver_library/python/src/ethosu_driver/__init__.py
new file mode 100644
index 0000000..ee6ea1f
--- /dev/null
+++ b/driver_library/python/src/ethosu_driver/__init__.py
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-License-Identifier: Apache-2.0
+
+from ._generated.driver import Device, Inference, Network, Buffer
+from ._utilities import open_device, load_model, populate_buffers, \
+ allocate_buffers, get_results, InferenceRunner
diff --git a/driver_library/python/src/ethosu_driver/_generated/__init__.py b/driver_library/python/src/ethosu_driver/_generated/__init__.py
new file mode 100644
index 0000000..6a36657
--- /dev/null
+++ b/driver_library/python/src/ethosu_driver/_generated/__init__.py
@@ -0,0 +1,2 @@
+# SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-License-Identifier: Apache-2.0
diff --git a/driver_library/python/src/ethosu_driver/_utilities/__init__.py b/driver_library/python/src/ethosu_driver/_utilities/__init__.py
new file mode 100644
index 0000000..c0dc1eb
--- /dev/null
+++ b/driver_library/python/src/ethosu_driver/_utilities/__init__.py
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-License-Identifier: Apache-2.0
+
+from .driver_utilities import open_device, load_model, populate_buffers, \
+ allocate_buffers, get_results, InferenceRunner
diff --git a/driver_library/python/src/ethosu_driver/_utilities/driver_utilities.py b/driver_library/python/src/ethosu_driver/_utilities/driver_utilities.py
new file mode 100644
index 0000000..216895a
--- /dev/null
+++ b/driver_library/python/src/ethosu_driver/_utilities/driver_utilities.py
@@ -0,0 +1,186 @@
+# SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-License-Identifier: Apache-2.0
+import logging
+import time
+from typing import List
+from .._generated.driver import Device, Inference, Network, Buffer, InferenceStatus_OK
+
+
+def open_device(device: str) -> Device:
+ """Opens the Ethos-U device file descriptor.
+
+ Args:
+ device: device name.
+
+ Returns:
+ `Device`: Return the object that represents Ethos-U device file descriptor and manages Ethos-U device lifecycle.
+ """
+ device = Device("/dev/{}".format(device))
+ return device
+
+
+def load_model(device: Device, model: str) -> Network:
+ """Create a `Network` when providing `Device` object and a string containing tflite file path.
+
+ Args:
+ device: `Device` object that Ethos-U device file descriptor.
+ model: tflite model file path .
+
+ Returns:
+ `Network`: Return the object that represent the neural __network file descriptor received from the Ethos-U device.
+ """
+ logging.info("Creating network")
+ network_buffer = Buffer(device, model)
+ return Network(device, network_buffer)
+
+
+def populate_buffers(input_data: List[bytearray], buffers: List[Buffer]):
+ """Set the feature maps associated memory buffer with the given data.
+
+ Args:
+ input_data: list of input feature maps data.
+ buffers: list of already initialized ifm buffers.
+ Raises:
+ RuntimeError: if input data size is incorrect.
+ """
+ number_of_buffers = len(buffers)
+
+ if number_of_buffers != len(input_data):
+ raise RuntimeError("Incorrect number of inputs, expected {}, got {}.".format(number_of_buffers, len(input_data)))
+
+ for index, (buffer, data_chunk) in enumerate(zip(buffers, input_data)):
+ cap = buffer.capacity()
+ logging.info("Copying data to a buffer {} of {} with size = {}".format(index + 1, number_of_buffers, cap))
+
+ if len(data_chunk) > cap:
+ raise RuntimeError("Buffer expects {} bytes, got {} bytes.".format(cap, len(data_chunk)))
+ buffer.resize(len(data_chunk))
+ buffer.from_buffer(data_chunk)
+
+
+def allocate_buffers(device: Device, dimensions: List) -> List[Buffer]:
+ """Returns output feature maps associated with memory buffers.
+
+ Args:
+ device: `Device` object that Ethos-U device file descriptor.
+ dimensions: `Network` object that represent the neural __network file descriptor.
+
+ Returns:
+ list: output feature map buffers.
+ """
+ buffers = []
+ total = len(dimensions)
+ for index, size in enumerate(dimensions):
+ logging.info("Allocating {} of {} buffer with size = {}".format(index + 1, total, size))
+ buffer = Buffer(device, size)
+ buffers.append(buffer)
+
+ return buffers
+
+
+def get_results(inference: Inference) -> List[Buffer]:
+ """Retrieves output inference buffers
+
+ Args:
+ inference: `Inference` object that represents the inference file descriptor.
+
+ Returns:
+ list: list of buffer objects
+ Raises:
+ RuntimeError: in case of inference returned failure status.
+
+ """
+ if InferenceStatus_OK != inference.status():
+ raise RuntimeError("Inference failed!")
+ else:
+ logging.info("Inference succeeded!")
+ return inference.getOfmBuffers()
+
+
+class InferenceRunner:
+ """Helper class to execute inference."""
+
+ def __init__(self, device_name: str, model: str):
+ """Initialises instance to execute inferences on the given model with given device
+
+ Device is opened with the name '/dev/<device_name>'.
+ Input/Output feature maps memory is allocated.
+
+ Args:
+ device_name: npu device name
+ model: Tflite model file path
+ """
+ self.__device = open_device(device_name)
+ if not InferenceRunner.wait_for_ping(self.__device, 3):
+ raise RuntimeError("Failed to communicate with device {}".format(device_name))
+
+ self.__network = load_model(self.__device, model)
+ # it is important to have a reference to current inference object to have access to OFMs.
+ self.__inf = None
+ self.__enabled_counters = ()
+
+ @staticmethod
+ def wait_for_ping(device: Device, count: int) -> bool:
+ if count == 0:
+ return False
+ try:
+ device.ping()
+ return True
+ except:
+ logging.info("Waiting for device: {}".format(count))
+ time.sleep(0.5)
+ return InferenceRunner.wait_for_ping(device, count-1)
+
+ def set_enabled_counters(self, enabled_counters: List[int] = ()):
+ """Set the enabled performance counter to use during inference.
+
+ Args:
+ enabled_counters: list of integer counter to enable.
+ Raises:
+ ValueError: in case of inference returned failure status or the Pmu counter requests exceed the maximum supported.
+ """
+ max_pmu_events = Inference.getMaxPmuEventCounters()
+ if len(enabled_counters) > max_pmu_events:
+ raise ValueError("Number of PMU counters requested exceed the maximum supported ({}).".format(max_pmu_events))
+ self.__enabled_counters = enabled_counters
+
+ def run(self, input_data: List[bytearray], timeout: int) -> List[Buffer]:
+ """Run a inference with the given input feature maps data.
+
+ Args:
+ input_data: data list containing input data as binary arrays
+ timeout: inference timout in nano seconds
+
+ Returns:
+ list: list of buffer objects
+ """
+ ofms = allocate_buffers(self.__device, self.__network.getOfmDims())
+ ifms = allocate_buffers(self.__device, self.__network.getIfmDims())
+ populate_buffers(input_data, ifms)
+
+ self.__inf = Inference(
+ self.__network,
+ ifms,
+ ofms,
+ self.__enabled_counters,
+ True)
+
+ self.__inf.wait(int(timeout))
+ return get_results(self.__inf)
+
+ def get_pmu_counters(self) -> List:
+ """Return the PMU data for the inference run.
+
+ Returns:
+ list: pairs of PMU type and cycle count value
+ """
+ return list(zip(self.__enabled_counters, self.__inf.getPmuCounters()))
+
+ def get_pmu_total_cycles(self) -> int:
+ """
+ Returns the total cycle count, including idle cycles, as reported by
+ the PMU
+
+ Returns: total cycle count
+ """
+ return self.__inf.getCycleCounter()
diff --git a/driver_library/python/src/ethosu_driver/inference_runner.py b/driver_library/python/src/ethosu_driver/inference_runner.py
new file mode 100644
index 0000000..1998465
--- /dev/null
+++ b/driver_library/python/src/ethosu_driver/inference_runner.py
@@ -0,0 +1,100 @@
+#
+# SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-License-Identifier: Apache-2.0
+#
+from argparse import ArgumentParser
+import os
+import logging
+from pathlib import Path
+from typing import List
+
+import ethosu_driver as driver
+try:
+ import numpy as np
+ with_numpy = True
+except ImportError:
+ with_numpy = False
+
+
+def read_bin_file_to_buf(file_path: str) -> bytearray:
+ with open(file_path, 'rb') as f:
+ return bytearray(f.read())
+
+
+def read_npy_file_to_buf(file_path: str) -> bytearray:
+ ifm_arr = np.load(file_path).astype(dtype=np.int8, order='C')
+ return ifm_arr.flatten().data
+
+
+def read_ifms(ifm_files: List[str], use_npy: bool = False):
+ read_file_to_buf = read_npy_file_to_buf if use_npy else read_bin_file_to_buf
+ for ifm_file in ifm_files:
+ yield read_file_to_buf(ifm_file)
+
+
+def write_npy(dir: str, file_name: str, data: memoryview):
+ ar = np.frombuffer(data, dtype=np.int8)
+ file_path = os.path.join(dir, "{}.npy".format(file_name))
+ if os.path.isfile(file_path):
+ os.remove(file_path)
+ np.save(file_path, ar)
+ logging.info("File saved to {}".format(file_path))
+
+
+def write_bin_file(dir: str, file_name: str, data: memoryview):
+ file_path = os.path.join(dir, "{}.bin".format(file_name))
+ if os.path.isfile(file_path):
+ os.remove(file_path)
+ with open(file_path, "wb") as f:
+ f.write(data)
+ logging.info("File saved to {}".format(file_path))
+
+
+def write_ofm(buf: memoryview, ofm_index: int, model_path: str, output_dir: str, use_npy: bool = False):
+ write_buf_to_file = write_npy if use_npy else write_bin_file
+ model_file_name = Path(model_path).name
+ ofm_name = "{}_ofm_{}".format(model_file_name, ofm_index)
+ write_buf_to_file(output_dir, ofm_name, buf)
+
+
+def main():
+ format = "%(asctime)s %(levelname)s - %(message)s"
+ logging.basicConfig(format=format, level=logging.INFO)
+
+ parser = ArgumentParser()
+ parser.add_argument("--device", help="Npu device name. Default: ethosu0", default="ethosu0")
+ parser.add_argument("--model", help="Tflite model file path", required=True)
+ parser.add_argument("--timeout", help="Inference timout in seconds, Default: infinite", default=-1, type=int)
+ parser.add_argument("--inputs", nargs='+', help="list of files containing input feature maps", required=True)
+ parser.add_argument("--output_dir", help="directory to store inference results, output feature maps. "
+ "Default: current directory", default=os.getcwd())
+ parser.add_argument("--npy", help="Use npy input/output", default=0, type=int)
+ parser.add_argument("--profile_counters", help="Performance counters to profile", nargs=4, type=int, required=True)
+ args = parser.parse_args()
+
+ use_numpy = with_numpy & bool(int(args.npy))
+ if use_numpy:
+ logging.info("Running with numpy inputs/outputs")
+ else:
+ logging.info("Running with byte array inputs/outputs")
+
+ # @TODO: Discuss if this is needed anymore. Remove this commented line, if not.
+ # driver.reset()
+
+ ifms_data = read_ifms(args.inputs, use_numpy)
+
+ runner = driver.InferenceRunner(args.device, args.model)
+ runner.set_enabled_counters(args.profile_counters)
+ ofm_buffers = runner.run(list(ifms_data), int(args.timeout))
+
+ for index, buffer_out in enumerate(ofm_buffers):
+ logging.info("Output buffer size: {}".format(buffer_out.size()))
+ write_ofm(buffer_out.data(), index, args.model, args.output_dir, use_numpy)
+
+ inference_pmu_counters = runner.get_pmu_counters()
+
+ # Profiling
+ total_cycles = runner.get_pmu_total_cycles()
+ for pmu, value in inference_pmu_counters:
+ logging.info("\tNPU %d counter: %d", pmu, value)
+ logging.info("\tNPU TOTAL cycles: %d", total_cycles)
diff --git a/driver_library/python/src/ethosu_driver/swig/driver.i b/driver_library/python/src/ethosu_driver/swig/driver.i
new file mode 100644
index 0000000..4cd8bdf
--- /dev/null
+++ b/driver_library/python/src/ethosu_driver/swig/driver.i
@@ -0,0 +1,554 @@
+//
+// SPDX-FileCopyrightText: Copyright 2020, 2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+// SPDX-License-Identifier: Apache-2.0
+//
+%module driver
+%{
+#define SWIG_FILE_WITH_INIT
+%}
+
+//typemap definitions and other common stuff
+%include "standard_header.i"
+
+%{
+#include "ethosu.hpp"
+#include <fstream>
+#include <list>
+#include <string>
+#include <cstring>
+#include <sstream>
+#include <linux/ioctl.h>
+
+#define ETHOSU_IOCTL_BASE 0x01
+#define ETHOSU_IO(nr) _IO(ETHOSU_IOCTL_BASE, nr)
+#define ETHOSU_IOCTL_PING ETHOSU_IO(0x00)
+
+%}
+%include <typemaps/buffer.i>
+
+%shared_ptr(EthosU::Buffer);
+%shared_ptr(EthosU::Network);
+
+
+namespace std {
+ %template(UintVector) vector<unsigned int>;
+ %template(SizeTVector) vector<size_t>;
+ %template(SharedBufferVector) vector<shared_ptr<EthosU::Buffer>>;
+}
+
+namespace EthosU
+{
+
+%feature("docstring",
+"
+Semantic Version : major.minor.patch
+") SemanticVersion;
+%nodefaultctor SemanticVersion;
+class SemanticVersion {
+public:
+ SemanticVersion(uint32_t major = 0, uint32_t minor = 0, uint32_t patch = 0);
+
+ uint32_t major;
+ uint32_t minor;
+ uint32_t patch;
+};
+
+%extend SemanticVersion {
+ std::string __str__() const {
+ std::ostringstream out;
+ out << *$self;
+ return out.str();
+ }
+}
+
+%feature("docstring",
+"
+Hardware Identifier which consists of version status, version revision, product revision and architecture revision.
+") HardwareId;
+class HardwareId {
+public:
+ HardwareId(uint32_t versionStatus, SemanticVersion& version, SemanticVersion& product, SemanticVersion& arch);
+
+ uint32_t versionStatus{0};
+ SemanticVersion version{};
+ SemanticVersion product{};
+ SemanticVersion architecture{};
+};
+
+%extend HardwareId {
+ std::string __str__() const {
+ std::ostringstream out;
+ out << "{versionStatus=" << $self->versionStatus <<
+ ", version=" << EthosU_SemanticVersion___str__(&$self->version) <<
+ ", product=" << EthosU_SemanticVersion___str__(&$self->product) <<
+ ", architecture=" << EthosU_SemanticVersion___str__(&$self->architecture) << "}";
+ return out.str();
+ }
+}
+
+%feature("docstring",
+"
+Hardware Configuration object defines specific configuration including MACs per clock cycle and NPU command stream
+version. This also specifies is custom DMA is enabled or not.
+") HardwareConfiguration;
+%nodefaultctor HardwareConfiguration;
+class HardwareConfiguration {
+ public:
+ HardwareConfiguration(uint32_t macs = 0, uint32_t cmdStreamVersion = 0, bool customDma = false);
+
+ uint32_t macsPerClockCycle;
+ uint32_t cmdStreamVersion;
+ bool customDma;
+};
+
+%extend HardwareConfiguration {
+ std::string __str__() const {
+ std::ostringstream out;
+ out << "{macsPerClockCycle=" << $self->macsPerClockCycle <<
+ ", cmdStreamVersion=" << $self->cmdStreamVersion <<
+ ", customDma=" << ($self->customDma? "True": "False") << "}";
+ return out.str();
+ }
+}
+
+%feature("docstring",
+"
+Device capabilities object which specifies capabilities based on hardware ID, configuration and semantic version.
+") Capabilities;
+class Capabilities {
+ public:
+ Capabilities() {}
+ Capabilities(const HardwareId& hwId, const HardwareConfiguration& hwCfg, const SemanticVersion& driverVersion);
+
+ HardwareId hwId;
+ HardwareConfiguration hwCfg;
+ SemanticVersion driver;
+};
+
+%extend Capabilities {
+ std::string __str__() const {
+ std::ostringstream out;
+ out << "{hwId=" << EthosU_HardwareId___str__(&$self->hwId) <<
+ ", hwCfg=" << EthosU_HardwareConfiguration___str__(&$self->hwCfg) <<
+ ", driver=" << EthosU_SemanticVersion___str__(&$self->driver) << "}";
+ return out.str();
+ }
+}
+
+%feature("docstring",
+"
+Device object represents Ethos-U device file descriptor and manages Ethos-U device lifecycle.
+Constructor accepts device name and opens file descriptor with O_RDWR | O_NONBLOCK flags.
+When the object is destroyed - device file descriptor is closed.
+") Device;
+%nodefaultctor Device;
+class Device {
+public:
+ Device(const char *device);
+
+ %feature("docstring",
+ "
+ Performs the I/O control operation on the Ethos-U device.
+
+ Args:
+ cmd: Command code
+ data: Command data
+ Returns:
+ int: Return value depends on command. Usually -1 indicates error.
+ ") ioctl;
+ int ioctl(unsigned long cmd, void *data = nullptr) const;
+
+ %feature("docstring",
+ "
+ Returns the capabilities of the Ethos-U device.
+
+ Returns:
+ Capabilities: Return capabilities of device.
+ ") capabilities;
+ Capabilities capabilities() const;
+};
+
+%extend Device {
+
+ %feature("docstring",
+ "
+ Sends ping command to the Ethos-U device.
+
+ See ETHOSU_IOCTL_PING from kernel module uapi/ethosu.h
+ ") ping;
+ void ping() {
+ $self->ioctl(ETHOSU_IOCTL_PING);
+ }
+}
+
+%feature("docstring",
+ "
+ Buffer object represents a RW mapping in the virtual address space of the caller.
+
+ Created mapping is shareable, updates to the mapping are visible to other processes mapping the same region.
+ Issues ETHOSU_IOCTL_BUFFER_CREATE I/O request to the device with given Maximum capacity.
+
+ Buffer could be created for a device with given maximum capacity or instantiated directly from
+ a file containing binary data.
+
+ Examples:
+ >>> import ethosu_driver as driver
+ >>> # from file:
+ >>> buf = driver.Buffer(device, '/path/to/file')
+ >>> # Empty, with maximum capacity:
+ >>> buf = driver.Buffer(device, 1024)
+ ") Buffer;
+%nodefaultctor Buffer;
+class Buffer {
+public:
+ Buffer(const Device &device, const size_t capacity);
+
+ %feature("docstring",
+ "
+ Returns maximum buffer capacity set during initialisation.
+
+ Returns:
+ int: maximum buffer capacity.
+ ") capacity;
+ size_t capacity() const;
+
+ %feature("docstring",
+ "
+ Sets the size of the device buffer to 0.
+ ") clear;
+ void clear() const;
+
+ %feature("docstring",
+ "
+ Returns a readonly view to the mapped memory.
+
+ Returns:
+ memoryview: readonly memory data.
+ ") data;
+ %driver_buffer_out;
+ char* data() const;
+ %clear_driver_buffer_out;
+
+ %feature("docstring",
+ "
+ Sets a size of the memory buffer for the device.
+
+ 'offset + size' must not exceed the capacity of the buffer.
+ Does not change the size of the mapped region.
+
+ Issues ETHOSU_IOCTL_BUFFER_SET I/O request with a given size and offset.
+
+ Args:
+ size (int): Device buffer size.
+ offset (int): Offset to where the data starts.
+ ") resize;
+ void resize(size_t size, size_t offset = 0) const;
+
+ %feature("docstring",
+ "
+ Queries device and returns buffer data offset.
+
+ Issues ETHOSU_IOCTL_BUFFER_GET I/O request.
+
+ Returns:
+ int: data offset
+ ") offset;
+ size_t offset() const;
+
+ %feature("docstring",
+ "
+ Queries device and returns buffer data size.
+
+ Issues ETHOSU_IOCTL_BUFFER_GET I/O request.
+
+ Returns:
+ int: current device buffer size.
+ ") size;
+ size_t size() const;
+
+ %feature("docstring",
+ "
+ Returns buffer file descriptor id.
+
+ Returns:
+ int: file descriptor id.
+ ") getFd;
+ int getFd() const;
+};
+
+%extend Buffer {
+
+ Buffer(const Device& device, const std::string& filename) {
+ std::ifstream stream(filename, std::ios::binary);
+ if (!stream.is_open()) {
+ throw EthosU::Exception(std::string("Failed to open file: ").append(filename).c_str());
+ }
+
+ stream.seekg(0, std::ios_base::end);
+ size_t size = stream.tellg();
+ stream.seekg(0, std::ios_base::beg);
+
+ auto buffer = new EthosU::Buffer(device, size);
+ buffer->resize(size);
+ stream.read(buffer->data(), size);
+
+ return buffer;
+ }
+
+ %feature("docstring",
+ "
+ Fills the buffer from python buffer.
+
+ Copies python buffer data to the mapped memory region.
+ Input buffer size must be within `Buffer` maximum capacity.
+
+ Args:
+ buffer: data to be copied to the mapped memory.
+
+ ") from_buffer;
+ %mutable_buffer(char* buffer, size_t size);
+ void from_buffer(char* buffer, size_t size) {
+ self->resize(size);
+ char* data = $self->data();
+ std::memcpy(data, buffer, size);
+ }
+ %clear_mutable_buffer(char* buffer, size_t size);
+}
+
+%feature("docstring",
+ "
+ Represents the neural network file descriptor received from the Ethos-U device.
+
+ `Network` is created providing `Device` object and a `Buffer` containing tflite file data.
+ Network creation issues ETHOSU_IOCTL_NETWORK_CREATE I/O request with buffer file descriptor id.
+ Provided `Buffer` data is parsed into tflite Model object and input/output feature maps sizes are saved.
+
+ Destruction of the object closes network file descriptor.
+ ") Network;
+%nodefaultctor Network;
+class Network {
+public:
+
+ %feature("docstring",
+ "
+ Performs the I/O control operation with network buffer device.
+
+ Args:
+ cmd: Command code
+ data: Command data
+ Returns:
+ int: Return value depends on command. Usually -1 indicates error.
+ ") ioctl;
+ int ioctl(unsigned long cmd, void *data);
+
+ %feature("docstring",
+ "
+ Returns associated memory buffer.
+
+ Returns:
+ `Buffer`: buffer object used during initialisation.
+ ") getBuffer;
+ std::shared_ptr<Buffer> getBuffer();
+
+ %feature("docstring",
+ "
+ Returns saved sizes of the neural network model input feature maps.
+
+ Returns:
+ list: sizes of all input feature maps
+ ") getIfmDims;
+ const std::vector<size_t> &getIfmDims() const;
+
+ %feature("docstring",
+ "
+ Returns total size of all input feature maps.
+
+ Returns:
+ int: total size of all input feature maps
+ ") getIfmSize;
+ size_t getIfmSize() const;
+
+ %feature("docstring",
+ "
+ Returns saved sizes of the neural network model output feature maps.
+
+ Returns:
+ list: sizes of all output feature maps
+ ") getOfmDims;
+ const std::vector<size_t> &getOfmDims() const;
+
+ %feature("docstring",
+ "
+ Returns total size of all output feature maps.
+
+ Returns:
+ int: total size of all output feature maps
+ ") getOfmSize;
+ size_t getOfmSize() const;
+};
+
+%extend Network {
+ Network(const Device &device, std::shared_ptr<Buffer> &buffer)
+ {
+ if(buffer == nullptr){
+ throw EthosU::Exception(std::string("Failed to create the network, buffer is nullptr.").c_str());
+ }
+ auto network = new EthosU::Network(device, buffer);
+ return network;
+ }
+}
+
+%extend Network {
+ Network(const Device &device, const unsigned int index)
+ {
+ auto network = new EthosU::Network(device, index);
+ return network;
+ }
+}
+
+%feature("docstring",
+ "
+ InferenceStatus enumeration
+ ") InferenceStatus;
+enum class InferenceStatus {
+ OK,
+ ERROR,
+ RUNNING,
+ REJECTED,
+ ABORTED,
+ ABORTING
+ };
+
+%feature("docstring",
+ "
+ Represents the inference file descriptor received from the Ethos-U device.
+
+ `Inference` is created providing `Network` object and lists of input and output feature maps buffers.
+ Feature map buffers are copied.
+
+ Inference creation issues ETHOSU_IOCTL_INFERENCE_CREATE I/O request with
+ file descriptor ids for all input and output buffers.
+
+ The number of input/output buffers must not exceed ETHOSU_FD_MAX value defined in the kernel module
+ uapi/ethosu.h.
+
+ Destruction of the object closes inference file descriptor.
+ ") Inference;
+%nodefaultctor Inference;
+class Inference {
+public:
+
+ %feature("docstring",
+ "
+ Polls inference file descriptor for events.
+
+ Args:
+ timeoutNanos (int64_t): polling timeout in nanoseconds.
+
+ Returns:
+ bool: True for success, False otherwise.
+ ") wait;
+ void wait(int64_t timeoutNanos = -1) const;
+
+ %feature("docstring",
+ "
+ Aborts the current inference job.
+
+ Returns:
+ bool: True if gracefully stopped, False otherwise.
+ ") cancel;
+ bool cancel() const;
+
+ %feature("docstring",
+ "
+ Gets the current inference job status.
+
+ Returns:
+ InferenceStatus.
+ ") status;
+ EthosU::InferenceStatus status() const;
+
+ %feature("docstring",
+ "
+ Returns inference file descriptor.
+
+ Returns:
+ int: file descriptor id
+ ") getFd;
+ int getFd() const;
+
+ %feature("docstring",
+ "
+ Returns associated `Network` object.
+
+ Returns:
+ `Network`: network used during initialisation
+ ") getNetwork;
+ std::shared_ptr<Network> getNetwork() const;
+
+ %feature("docstring",
+ "
+ Returns copied input feature maps buffers.
+
+ Returns:
+ list: input feature map buffers
+ ") getIfmBuffers;
+ std::vector<std::shared_ptr<Buffer>> &getIfmBuffers();
+
+ %feature("docstring",
+ "
+ Returns copied output feature maps buffers.
+
+ Returns:
+ list: output feature map buffers
+ ") getOfmBuffers;
+ std::vector<std::shared_ptr<Buffer>> &getOfmBuffers();
+
+ %feature("docstring",
+ "
+ Returns PMU event data.
+
+ Returns:
+ list: PMU event data
+ ") getPmuCounters;
+ const std::vector<uint32_t> getPmuCounters();
+
+ %feature("docstring",
+ "
+ Returns the total cycle count, including idle cycles, as reported by the PMU.
+
+ Returns:
+ int: total cycle count
+ ") getCycleCounter;
+ uint64_t getCycleCounter();
+
+ %feature("docstring",
+ "
+ Returns maximum supported number of PMU events.
+
+ Returns:
+ int: PMU event max
+ ") getMaxPmuEventCounters;
+ static uint32_t getMaxPmuEventCounters();
+};
+
+%extend Inference {
+ Inference(const std::shared_ptr<Network> &network,
+ const std::vector<std::shared_ptr<Buffer>> &ifm,
+ const std::vector<std::shared_ptr<Buffer>> &ofm)
+ {
+ return new EthosU::Inference(network, ifm.begin(), ifm.end(), ofm.begin(), ofm.end());
+ }
+ Inference(const std::shared_ptr<Network> & network,
+ const std::vector<std::shared_ptr<Buffer>> &ifm,
+ const std::vector<std::shared_ptr<Buffer>> &ofm,
+ const std::vector<unsigned int> &enabledCounters,
+ bool enableCycleCounter)
+ {
+ return new EthosU::Inference(network, ifm.begin(), ifm.end(), ofm.begin(), ofm.end(), enabledCounters, enableCycleCounter);
+ }
+}
+
+}
+// Clear exception typemap.
+%exception;
diff --git a/driver_library/python/src/ethosu_driver/swig/standard_header.i b/driver_library/python/src/ethosu_driver/swig/standard_header.i
new file mode 100644
index 0000000..03cf015
--- /dev/null
+++ b/driver_library/python/src/ethosu_driver/swig/standard_header.i
@@ -0,0 +1,50 @@
+//
+// SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+// SPDX-License-Identifier: Apache-2.0
+//
+%include "stl.i"
+%include "cstring.i"
+%include "std_string.i"
+%include "std_vector.i"
+%include "std_unordered_set.i"
+%include "std_pair.i"
+%include "stdint.i"
+%include "carrays.i"
+%include "exception.i"
+%include "typemaps.i"
+%include "std_iostream.i"
+%include "std_shared_ptr.i"
+
+%ignore *::operator=;
+%ignore *::operator[];
+
+
+// Define exception typemap to wrap exception into python exception.
+
+%exception{
+ try {
+ $action
+ } catch (const EthosU::Exception& e) {
+ SWIG_exception(SWIG_RuntimeError, const_cast<char*>(e.what()));
+ }
+};
+
+%exception __getitem__ {
+ try {
+ $action
+ } catch (const std::out_of_range &e) {
+ SWIG_exception(SWIG_IndexError, const_cast<char*>(e.what()));
+ } catch (const std::exception &e) {
+ SWIG_exception(SWIG_RuntimeError, const_cast<char*>(e.what()));
+ }
+};
+
+%exception __setitem__ {
+ try {
+ $action
+ } catch (const std::out_of_range &e) {
+ SWIG_exception(SWIG_IndexError, const_cast<char*>(e.what()));
+ } catch (const std::exception &e) {
+ SWIG_exception(SWIG_RuntimeError, const_cast<char*>(e.what()));
+ }
+};
diff --git a/driver_library/python/src/ethosu_driver/swig/typemaps/buffer.i b/driver_library/python/src/ethosu_driver/swig/typemaps/buffer.i
new file mode 100644
index 0000000..13b7909
--- /dev/null
+++ b/driver_library/python/src/ethosu_driver/swig/typemaps/buffer.i
@@ -0,0 +1,42 @@
+//
+// SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+// SPDX-License-Identifier: Apache-2.0
+//
+%define %mutable_buffer(TYPEMAP, SIZE)
+ %typemap(in) (TYPEMAP, SIZE) {
+ int res; void *buf = 0; size_t size = 0;
+ Py_buffer view;
+ res = PyObject_GetBuffer($input, &view, PyBUF_WRITABLE);
+ buf = view.buf;
+ size = view.len;
+ PyBuffer_Release(&view);
+ if (res < 0) {
+ PyErr_Clear();
+ %argument_fail(res, "(TYPEMAP, SIZE)", $symname, $argnum);
+ }
+ $1 = ($1_ltype) buf;
+ $2 = ($2_ltype) size;
+ }
+
+ %typemap(typecheck) (TYPEMAP, SIZE) {
+ $1 = PyObject_CheckBuffer($input) || PyTuple_Check($input) ? 1 : 0;
+ }
+%enddef
+
+%define %clear_mutable_buffer(TYPEMAP, SIZE)
+ %typemap(in) (TYPEMAP, SIZE);
+ %typemap(typecheck) (TYPEMAP, SIZE);
+%enddef
+
+
+%define %driver_buffer_out
+ %typemap(out) (char*) {
+ auto size = arg1->size();
+ int readOnly = 0;
+ $result = PyMemoryView_FromMemory($1, size, readOnly);
+ }
+%enddef
+
+%define %clear_driver_buffer_out
+ %typemap(out) (char*);
+%enddef
diff --git a/driver_library/python/swig_generate.py b/driver_library/python/swig_generate.py
new file mode 100755
index 0000000..bdd43a3
--- /dev/null
+++ b/driver_library/python/swig_generate.py
@@ -0,0 +1,27 @@
+# SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-License-Identifier: Apache-2.0
+"""
+This script executes SWIG commands to generate C++ library wrappers.
+"""
+import subprocess
+from pathlib import Path
+
+__current_dir = Path(__file__).parent.absolute()
+
+
+def generate_wrap(name, extr_includes):
+ print('Generating wrappers for {}'.format(name))
+ subprocess.check_output("swig -v -c++ -python" +
+ " -Wall" +
+ " -o {}/src/ethosu_driver/_generated/{}_wrap.cpp ".format(__current_dir, name) +
+ "-outdir {}/src/ethosu_driver/_generated ".format(__current_dir) +
+ "{} ".format(extr_includes) +
+ "-I{}/src/ethosu_driver/swig ".format(__current_dir) +
+ "{}/src/ethosu_driver/swig/{}.i".format(__current_dir, name),
+ shell=True,
+ stderr=subprocess.STDOUT)
+
+
+if __name__ == "__main__":
+ includes = ["{}/../../driver_library/include".format(__current_dir)]
+ generate_wrap('driver', "-I{} ".format(' -I'.join(includes)))
diff --git a/driver_library/python/test/conftest.py b/driver_library/python/test/conftest.py
new file mode 100644
index 0000000..8eef1f8
--- /dev/null
+++ b/driver_library/python/test/conftest.py
@@ -0,0 +1,34 @@
+#
+# SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-License-Identifier: Apache-2.0
+#
+import os
+import pytest
+
+
+@pytest.fixture(scope="module")
+def data_folder_per_test(request):
+ """
+ This fixture returns path to folder with test resources (one per test module)
+ """
+
+ basedir, script = request.fspath.dirname, request.fspath.basename
+ return str(os.path.join(basedir, "testdata", os.path.splitext(script)[0]))
+
+
+@pytest.fixture(scope="module")
+def shared_data_folder(request):
+ """
+ This fixture returns path to folder with shared test resources among all tests
+ """
+
+ return str(os.path.join(request.fspath.dirname, "testdata", "shared"))
+
+
+@pytest.fixture(scope="function")
+def tmpdir(tmpdir):
+ """
+ This fixture returns path to temp folder. Fixture was added for py35 compatibility
+ """
+
+ return str(tmpdir)
diff --git a/driver_library/python/test/test_capabilities.py b/driver_library/python/test/test_capabilities.py
new file mode 100644
index 0000000..ffb201c
--- /dev/null
+++ b/driver_library/python/test/test_capabilities.py
@@ -0,0 +1,73 @@
+#
+# SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-License-Identifier: Apache-2.0
+#
+from ethosu_driver._generated.driver import SemanticVersion
+from ethosu_driver._generated.driver import HardwareId
+from ethosu_driver._generated.driver import HardwareConfiguration
+from ethosu_driver._generated.driver import Capabilities
+
+
+def check_semantic_version(ma, mi, pa, sv):
+ assert ma == sv.major
+ assert mi == sv.minor
+ assert pa == sv.patch
+
+
+def test_semantic_version():
+ sv = SemanticVersion(1, 2, 3)
+ assert '{ major=1, minor=2, patch=3 }' == sv.__str__()
+ check_semantic_version(1, 2, 3, sv)
+
+
+def test_hardware_id():
+ version = SemanticVersion(1, 2, 3)
+ product = SemanticVersion(4, 5, 6)
+ architecture = SemanticVersion(7, 8, 9)
+ hw_id = HardwareId(1, version, product, architecture)
+
+ assert 1 == hw_id.versionStatus
+
+ check_semantic_version(1, 2, 3, hw_id.version)
+ check_semantic_version(4, 5, 6, hw_id.product)
+ check_semantic_version(7, 8, 9, hw_id.architecture)
+
+ assert '{versionStatus=1, version={ major=1, minor=2, patch=3 }, product={ major=4, minor=5, patch=6 }, ' \
+ 'architecture={ major=7, minor=8, patch=9 }}' == hw_id.__str__()
+
+
+def test_hw_configuration():
+ hw_cfg = HardwareConfiguration(128, 1, True)
+
+ assert 1 == hw_cfg.cmdStreamVersion
+ assert 128 == hw_cfg.macsPerClockCycle
+ assert hw_cfg.customDma
+
+ assert "{macsPerClockCycle=128, cmdStreamVersion=1, customDma=True}" == hw_cfg.__str__()
+
+
+def test_capabilities():
+ version = SemanticVersion(100, 200, 300)
+ product = SemanticVersion(400, 500, 600)
+ architecture = SemanticVersion(700, 800, 900)
+ hw_id = HardwareId(1, version, product, architecture)
+ hw_cfg = HardwareConfiguration(256, 1000, False)
+ driver_v = SemanticVersion(10, 20, 30)
+
+ cap = Capabilities(hw_id, hw_cfg, driver_v)
+
+ check_semantic_version(10, 20, 30, cap.driver)
+
+ check_semantic_version(100, 200, 300, cap.hwId.version)
+ check_semantic_version(400, 500, 600, cap.hwId.product)
+ check_semantic_version(700, 800, 900, cap.hwId.architecture)
+
+ assert 1000 == cap.hwCfg.cmdStreamVersion
+ assert 256 == cap.hwCfg.macsPerClockCycle
+ assert not cap.hwCfg.customDma
+
+ assert '{hwId={versionStatus=1, version={ major=100, minor=200, patch=300 }, ' \
+ 'product={ major=400, minor=500, patch=600 }, ' \
+ 'architecture={ major=700, minor=800, patch=900 }}, ' \
+ 'hwCfg={macsPerClockCycle=256, cmdStreamVersion=1000, customDma=False}, ' \
+ 'driver={ major=10, minor=20, patch=30 }}' == cap.__str__()
diff --git a/driver_library/python/test/test_driver.py b/driver_library/python/test/test_driver.py
new file mode 100644
index 0000000..5496aed
--- /dev/null
+++ b/driver_library/python/test/test_driver.py
@@ -0,0 +1,179 @@
+#
+# SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-License-Identifier: Apache-2.0
+#
+import pytest
+import os
+import ethosu_driver as driver
+from ethosu_driver.inference_runner import read_npy_file_to_buf
+
+
+@pytest.fixture()
+def device(device_name):
+ device = driver.Device("/dev/{}".format(device_name))
+ yield device
+
+
+@pytest.fixture()
+def network_buffer(device, model_name, shared_data_folder):
+ network_file = os.path.join(shared_data_folder, model_name)
+ network_buffer = driver.Buffer(device, network_file)
+ yield network_buffer
+
+
+@pytest.mark.parametrize('device_name', ['ethosu0'])
+def test_check_device_swig_ownership(device):
+ # Check to see that SWIG has ownership for parser. This instructs SWIG to take
+ # ownership of the return value. This allows the value to be automatically
+ # garbage-collected when it is no longer in use
+ assert device.thisown
+
+
+@pytest.mark.parametrize('device_name', ['ethosu0'])
+def test_device_ping(device):
+ device.ping()
+
+
+@pytest.mark.parametrize('device_name', ['blabla'])
+def test_device_wrong_name(device_name):
+ with pytest.raises(RuntimeError) as err:
+ driver.Device("/dev/{}".format(device_name))
+ # Only check for part of the exception since the exception returns
+ # absolute path which will change on different machines.
+ assert 'Failed to open device' in str(err.value)
+
+
+@pytest.mark.parametrize('device_name', ['ethosu0'])
+def test_driver_network_filenotfound_exception(device, shared_data_folder):
+
+ network_file = os.path.join(shared_data_folder, "some_unknown_model.tflite")
+
+ with pytest.raises(RuntimeError) as err:
+ network_buffer = driver.Buffer(device, network_file)
+
+ # Only check for part of the exception since the exception returns
+ # absolute path which will change on different machines.
+ assert 'Failed to open file:' in str(err.value)
+
+
+@pytest.mark.parametrize('device_name', ['ethosu0'])
+@pytest.mark.parametrize('model_name', ['model.tflite'])
+def test_check_buffer_swig_ownership(network_buffer):
+ # Check to see that SWIG has ownership for parser. This instructs SWIG to take
+ # ownership of the return value. This allows the value to be automatically
+ # garbage-collected when it is no longer in use
+ assert network_buffer.thisown
+
+
+@pytest.mark.parametrize('device_name', ['ethosu0'])
+@pytest.mark.parametrize('model_name', ['model.tflite'])
+def test_check_buffer_capacity(network_buffer):
+ assert network_buffer.capacity() > 0
+
+
+@pytest.mark.parametrize('device_name', ['ethosu0'])
+@pytest.mark.parametrize('model_name', ['model.tflite'])
+def test_check_buffer_size(network_buffer):
+ assert network_buffer.size() > 0
+
+
+@pytest.mark.parametrize('device_name', ['ethosu0'])
+@pytest.mark.parametrize('model_name', ['model.tflite'])
+def test_check_buffer_clear(network_buffer):
+ network_buffer.clear()
+ assert network_buffer.size() == 0
+
+
+@pytest.mark.parametrize('device_name', ['ethosu0'])
+@pytest.mark.parametrize('model_name', ['model.tflite'])
+def test_check_buffer_resize(network_buffer):
+ offset = 1
+ new_size = network_buffer.capacity() - offset
+ network_buffer.resize(new_size, offset)
+ assert network_buffer.size() == new_size
+
+
+@pytest.mark.parametrize('device_name', ['ethosu0'])
+@pytest.mark.parametrize('model_name', ['model.tflite'])
+def test_check_buffer_getFd(network_buffer):
+ assert network_buffer.getFd() >= 0
+
+
+@pytest.mark.parametrize('device_name', ['ethosu0'])
+@pytest.mark.parametrize('model_name', ['model.tflite'])
+def test_check_network_ifm_size(device, network_buffer):
+ network = driver.Network(device, network_buffer)
+ assert network.getIfmSize() > 0
+ assert network_buffer.thisown
+
+
+@pytest.mark.parametrize('device_name', [('ethosu0')])
+def test_check_network_buffer_none(device):
+
+ with pytest.raises(RuntimeError) as err:
+ driver.Network(device, None)
+
+ # Only check for part of the exception since the exception returns
+ # absolute path which will change on different machines.
+ assert 'Failed to create the network' in str(err.value)
+
+
+@pytest.mark.parametrize('device_name', ['ethosu0'])
+@pytest.mark.parametrize('model_name', ['model.tflite'])
+def test_check_network_ofm_size(device, network_buffer):
+ network = driver.Network(device, network_buffer)
+ assert network.getOfmSize() > 0
+
+
+def test_getMaxPmuEventCounters():
+ assert driver.Inference.getMaxPmuEventCounters() > 0
+
+
+@pytest.fixture()
+def inf(device_name, model_name, input_files, timeout, shared_data_folder):
+ # Prepate full path of model and inputs
+ full_path_model_file = os.path.join(shared_data_folder, model_name)
+ full_path_input_files = []
+ for input_file in input_files:
+ full_path_input_files.append(os.path.join(shared_data_folder, input_file))
+
+ ifms_data = []
+ for ifm_file in full_path_input_files:
+ ifms_data.append(read_npy_file_to_buf(ifm_file))
+
+ device = driver.open_device(device_name)
+ device.ping()
+ network = driver.load_model(device, full_path_model_file)
+ ofms = driver.allocate_buffers(device, network.getOfmDims())
+ ifms = driver.allocate_buffers(device, network.getIfmDims())
+
+ # ofm_buffers = runner.run(ifms_data,timeout, ethos_pmu_counters)
+ driver.populate_buffers(ifms_data, ifms)
+ ethos_pmu_counters = [1]
+ enable_cycle_counter = True
+ inf_inst = driver.Inference(network, ifms, ofms, ethos_pmu_counters, enable_cycle_counter)
+ inf_inst.wait(int(timeout))
+
+ yield inf_inst
+
+
+@pytest.mark.parametrize('device_name, model_name, timeout, input_files',
+ [('ethosu0', 'model.tflite', 5000000000, ['model_ifm.npy'])])
+def test_inf_get_cycle_counter(inf):
+ total_cycles = inf.getCycleCounter()
+ assert total_cycles >= 0
+
+
+@pytest.mark.parametrize('device_name, model_name, timeout, input_files',
+ [('ethosu0', 'model.tflite', 5000000000, ['model_ifm.npy'])])
+def test_inf_get_pmu_counters(inf):
+ inf_pmu_counter = inf.getPmuCounters()
+ assert len(inf_pmu_counter) > 0
+
+
+@pytest.mark.parametrize('device_name', ['ethosu0'])
+def test_capabilities(device):
+ cap = device.capabilities()
+ assert cap.hwId
+ assert cap.hwCfg
+ assert cap.driver
diff --git a/driver_library/python/test/test_driver_utilities.py b/driver_library/python/test/test_driver_utilities.py
new file mode 100644
index 0000000..fc8e921
--- /dev/null
+++ b/driver_library/python/test/test_driver_utilities.py
@@ -0,0 +1,77 @@
+#
+# SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-License-Identifier: Apache-2.0
+#
+import pytest
+import os
+import ethosu_driver as driver
+from ethosu_driver.inference_runner import read_npy_file_to_buf
+
+
+@pytest.fixture()
+def device(device_name):
+ device = driver.open_device(device_name)
+ yield device
+
+
+@pytest.fixture()
+def network(device, model_name, shared_data_folder):
+ network_file = os.path.join(shared_data_folder, model_name)
+ network = driver.load_model(device, network_file)
+ yield network
+
+
+@pytest.mark.parametrize('device_name', ['blabla'])
+def test_open_device_wrong_name(device_name):
+ with pytest.raises(RuntimeError) as err:
+ device = driver.open_device(device_name)
+ # Only check for part of the exception since the exception returns
+ # absolute path which will change on different machines.
+ assert 'Failed to open device' in str(err.value)
+
+
+@pytest.mark.parametrize('device_name', ['ethosu0'])
+def test_network_filenotfound_exception(device, shared_data_folder):
+
+ network_file = os.path.join(shared_data_folder, "some_unknown_model.tflite")
+
+ with pytest.raises(RuntimeError) as err:
+ driver.load_model(device, network_file)
+
+ # Only check for part of the exception since the exception returns
+ # absolute path which will change on different machines.
+ assert 'Failed to open file:' in str(err.value)
+
+
+@pytest.mark.parametrize('device_name', ['ethosu0'])
+@pytest.mark.parametrize('model_name', ['model.tflite'])
+def test_check_network_ifm_size(network):
+ assert network.getIfmSize() > 0
+
+
+@pytest.mark.parametrize('device_name', ['ethosu0'])
+def test_allocate_buffers(device):
+ buffers = driver.allocate_buffers(device, [128, 256])
+ assert len(buffers) == 2
+ assert buffers[0].size() == 0
+ assert buffers[0].capacity() == 128
+ assert buffers[1].size() == 0
+ assert buffers[1].capacity() == 256
+
+
+@pytest.mark.parametrize('device_name', ['ethosu0'])
+@pytest.mark.parametrize('model_name', ['model.tflite'])
+@pytest.mark.parametrize('ifms_file_list', [['model_ifm.npy']])
+def test_set_ifm_buffers(device, network, ifms_file_list, shared_data_folder):
+ full_path_input_files = []
+ for input_file in ifms_file_list:
+ full_path_input_files.append(os.path.join(shared_data_folder, input_file))
+
+ ifms_data = []
+ for ifm_file in full_path_input_files:
+ ifms_data.append(read_npy_file_to_buf(ifm_file))
+
+ ifms = driver.allocate_buffers(device, network.getIfmDims())
+ driver.populate_buffers(ifms_data, ifms)
+ assert len(ifms) > 0
+
diff --git a/driver_library/python/test/test_inference.py b/driver_library/python/test/test_inference.py
new file mode 100644
index 0000000..bfb4068
--- /dev/null
+++ b/driver_library/python/test/test_inference.py
@@ -0,0 +1,50 @@
+#
+# SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-License-Identifier: Apache-2.0
+#
+import pytest
+import os
+import ethosu_driver as driver
+from ethosu_driver.inference_runner import read_npy_file_to_buf
+
+
+def run_inference_test(runner, timeout, input_files, golden_outputs, shared_data_folder):
+
+ full_path_input_files = []
+ for input_file in input_files:
+ full_path_input_files.append(os.path.join(shared_data_folder, input_file))
+
+ ifms_data = []
+ for ifm_file in full_path_input_files:
+ ifms_data.append(read_npy_file_to_buf(ifm_file))
+
+ ofm_buffers = runner.run(ifms_data, timeout)
+
+ for index, buffer_out in enumerate(ofm_buffers):
+ golden_output = read_npy_file_to_buf(os.path.join(shared_data_folder, golden_outputs[index]))
+ assert buffer_out.data().nbytes == golden_output.nbytes
+ for index, golden_value in enumerate(golden_output):
+ assert golden_value == buffer_out.data()[index]
+
+
+@pytest.mark.parametrize('device_name, model_name, timeout, input_files, golden_outputs',
+ [('ethosu0', 'model.tflite', 5000000000, ['model_ifm.npy'], ['model_ofm.npy'])])
+def test_inference(device_name, model_name, input_files, timeout, golden_outputs, shared_data_folder):
+ # Prepate full path of model and inputs
+ full_path_model_file = os.path.join(shared_data_folder, model_name)
+
+ runner = driver.InferenceRunner(device_name, full_path_model_file)
+ run_inference_test(runner, timeout, input_files, golden_outputs, shared_data_folder)
+
+
+@pytest.mark.parametrize('device_name, model_name, timeout, input_files, golden_outputs',
+ [('ethosu0', 'model.tflite', 5000000000,
+ [['model_ifm.npy'], ['model_ifm.npy']],
+ [['model_ofm.npy'], ['model_ofm.npy']])])
+def test_inference_loop(device_name, model_name, input_files, timeout, golden_outputs, shared_data_folder):
+ # Prepare full path of model and inputs
+ full_path_model_file = os.path.join(shared_data_folder, model_name)
+
+ runner = driver.InferenceRunner(device_name, full_path_model_file)
+ for input_file, golden_output in zip(input_files, golden_outputs):
+ run_inference_test(runner, timeout, input_file, golden_output, shared_data_folder)
diff --git a/driver_library/python/test/test_shadow_classes.py b/driver_library/python/test/test_shadow_classes.py
new file mode 100644
index 0000000..055c10b
--- /dev/null
+++ b/driver_library/python/test/test_shadow_classes.py
@@ -0,0 +1,20 @@
+#
+# SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-License-Identifier: Apache-2.0
+#
+import inspect
+import pytest
+import ethosu_driver._generated.driver as driver_shadow
+
+
+def get_classes():
+ ignored_class_names = ('_SwigNonDynamicMeta', '_object', '_swig_property')
+ return list(filter(lambda x: x[0] not in ignored_class_names,
+ inspect.getmembers(driver_shadow, inspect.isclass)))
+
+
+@pytest.mark.parametrize("class_instance", get_classes(), ids=lambda x: 'class={}'.format(x[0]))
+class TestOwnership:
+
+ def test_destructors_exist_per_class(self, class_instance):
+ assert getattr(class_instance[1], '__swig_destroy__', None)
diff --git a/driver_library/python/test/testdata/download.py b/driver_library/python/test/testdata/download.py
new file mode 100644
index 0000000..18aa9af
--- /dev/null
+++ b/driver_library/python/test/testdata/download.py
@@ -0,0 +1,46 @@
+#
+# SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-License-Identifier: Apache-2.0
+#
+import os
+from pathlib import Path
+from typing import List
+from urllib.request import urlopen
+"""
+Downloads resources for tests from Arm public model zoo.
+Run this script before executing tests.
+"""
+
+
+PMZ_URL = 'https://github.com/ARM-software/ML-zoo/raw/9f506fe52b39df545f0e6c5ff9223f671bc5ae00/models'
+test_resources = [
+ {'model': '{}/visual_wake_words/micronet_vww2/tflite_int8/vww2_50_50_INT8.tflite'.format(PMZ_URL),
+ 'ifm': '{}/visual_wake_words/micronet_vww2/tflite_int8/testing_input/input/0.npy'.format(PMZ_URL),
+ 'ofm': '{}/visual_wake_words/micronet_vww2/tflite_int8/testing_output/Identity/0.npy'.format(PMZ_URL)}
+]
+
+
+def download(path: str, url: str):
+ with urlopen(url) as response, open(path, 'wb') as file:
+ print("Downloading {} ...".format(url))
+ file.write(response.read())
+ file.seek(0)
+ print("Finished downloading {}.".format(url))
+
+
+def download_test_resources(test_res_entries: List[dict], where_to: str):
+ os.makedirs(where_to, exist_ok=True)
+
+ for resources in test_res_entries:
+ download(os.path.join(where_to, 'model.tflite'), resources['model'])
+ download(os.path.join(where_to, 'model_ifm.npy'), resources['ifm'])
+ download(os.path.join(where_to, 'model_ofm.npy'), resources['ofm'])
+
+
+def main():
+ current_dir = str(Path(__file__).parent.absolute())
+ download_test_resources(test_resources, os.path.join(current_dir, 'shared'))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/driver_library/src/ethosu.cpp b/driver_library/src/ethosu.cpp
index 16b9654..e425e52 100644
--- a/driver_library/src/ethosu.cpp
+++ b/driver_library/src/ethosu.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020-2022 Arm Limited.
+ * SPDX-FileCopyrightText: Copyright 2020-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
*
* SPDX-License-Identifier: Apache-2.0
*
diff --git a/driver_library/src/ethosu_stub.cpp b/driver_library/src/ethosu_stub.cpp
index d85f653..e223be3 100644
--- a/driver_library/src/ethosu_stub.cpp
+++ b/driver_library/src/ethosu_stub.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020-2022 Arm Limited.
+ * SPDX-FileCopyrightText: Copyright 2020-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
*
* SPDX-License-Identifier: Apache-2.0
*