1#!/usr/bin/env bash
2#
3# SPDX-FileCopyrightText: Copyright 2024 Arm Limited and/or its affiliates <open-source-office@arm.com>
4#
5# SPDX-License-Identifier: Apache-2.0
6#
7# Licensed under the Apache License, Version 2.0 (the License); you may
8# not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an AS IS BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18#
19# Version: 1.0
20# Date: 2024-05-21
21# This bash script downloads unit test dependencies, builds unit tests and also runs the tests.
22
23CPU="cortex-m55"
24OPTIMIZATION="-Ofast"
25QUIET=0
26BUILD=1
27RUN=1
28SETUP_ENVIRONMENT=1
29USE_ARM_COMPILER=0
30USE_PYTHON_VENV=1
31USE_FVP_FROM_DOWNLOAD=1
32USE_GCC_FROM_DOWNLOAD=1
33
34ETHOS_U_CORE_PLATFORM_PATH=""
35CMSIS_5_PATH=""
36
37
38usage="
39Helper script to setup, build and run CMSIS-NN unit tests
40
41args:
42    -h  Display this message.
43    -c  Target cpu. Takes multiple arguments as a comma seperated list. eg cortex-m3,cortex-m7,cortex-m55 (default: cortex-m55)
44    -o  Optimization level. (default: '-Ofast')
45    -q  Quiet mode. This reduces the amount of info printed from building and running cmsis-unit tests.
46    -b  Disable CMake build. Only works with previously built targets. Designed to quickly rerun cpu targets.
47    -r  Disable running the unit tests. Designed to test build only or allow user to manually run individual test cases outside of this script.
48    -e  Disable environment setup. This flag will stop the script from attempting to download dependencies. This is just a quiet mode to reduce print outs.
49    -a  Use Arm Compiler that is previously available on machine. Ensure compiler directory is added to path and export CC.
50    -p  Disable the usage of python venv from download directory. Requires dependencies to be install before calling script.
51    -f  Disable the usage of FVP from download directory. Requires FVP to be in path before calling script.
52    -u  Path to ethos-u-core-platform
53    -g  Disable the usage of GCC that is already from download directory. Requires gcc to be in path before calling script.
54    -C  Path to cmsis 5
55
56    example usage: $(basename "$0") -c cortex-m3,cortex-m4 -o '-O2' -q
57"
58
59while getopts hc:o:qbreapfu:gC: flag
60do
61    case "${flag}" in
62        h) echo "${usage}"
63           exit 1;;
64        c) CPU=${OPTARG};;
65        o) OPTIMIZATION=${OPTARG};;
66        q) QUIET=1;;
67        b) BUILD=0;;
68        r) RUN=0;;
69        e) SETUP_ENVIRONMENT=0;;
70        a) USE_ARM_COMPILER=1;;
71        p) USE_PYTHON_VENV=0;;
72        f) USE_FVP_FROM_DOWNLOAD=0;;
73        u) ETHOS_U_CORE_PLATFORM_PATH="${OPTARG}";;
74        g) USE_GCC_FROM_DOWNLOAD=0;;
75        C) CMSIS_5_PATH="${OPTARG}";;
76    esac
77done
78
79Setup_Environment() {
80    set -e
81    echo "++ Downloading Corstone300"
82    if [[ -d ${WORKING_DIR}/corstone300_download ]]; then
83        echo "Corstone300 already installed. If you wish to install a new version, please delete the old folder."
84    else
85        if [[ ${UNAME_M} == x86_64 ]]; then
86            CORSTONE_URL=https://developer.arm.com/-/media/Arm%20Developer%20Community/Downloads/OSS/FVP/Corstone-300/FVP_Corstone_SSE-300_11.24_13_Linux64.tgz
87        elif [[ ${UNAME_M} == aarch64 ]]; then
88            CORSTONE_URL=https://developer.arm.com/-/media/Arm%20Developer%20Community/Downloads/OSS/FVP/Corstone-300/FVP_Corstone_SSE-300_11.24_13_Linux64_armv8l.tgz
89        fi
90
91        TEMPFILE=$(mktemp -d)/temp_file
92        wget ${CORSTONE_URL} -O ${TEMPFILE} >&2
93
94        TEMPDIR=$(mktemp -d)
95        tar -C ${TEMPDIR} -xvzf ${TEMPFILE} >&2
96        mkdir ${WORKING_DIR}/corstone300_download
97        ${TEMPDIR}/FVP_Corstone_SSE-300.sh --i-agree-to-the-contained-eula --no-interactive -d ${WORKING_DIR}/corstone300_download >&2
98    fi
99
100    echo "++ Downloading GCC"
101    if [[ -d ${WORKING_DIR}/arm_gcc_download ]]; then
102        echo "Arm GCC already installed. If you wish to install a new version, please delete the old folder."
103    else
104        if [[ ${UNAME_M} == x86_64 ]]; then
105            GCC_URL="https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x86_64-arm-none-eabi.tar.xz"
106        elif [[ ${UNAME_M} == aarch64 ]]; then
107            GCC_URL="https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-aarch64-arm-none-eabi.tar.xz"
108        fi
109
110        TEMPFILE=$(mktemp -d)/temp_file
111        wget ${GCC_URL} -O ${TEMPFILE} >&2
112        mkdir ${WORKING_DIR}/arm_gcc_download
113
114        tar -C ${WORKING_DIR}/arm_gcc_download --strip-components=1 -xJf ${TEMPFILE} >&2
115    fi
116
117    echo "++ Cloning CMSIS-5"
118    if [[ -d ${WORKING_DIR}/CMSIS_5 ]]; then
119        echo "CMSIS-5 already installed. If you wish to install a new version, please delete the old folder."
120    else
121        git clone https://github.com/ARM-software/CMSIS_5.git
122    fi
123
124    echo "++ Cloning Ethos-U core platform"
125    if [[ -d ${WORKING_DIR}/ethos-u-core-platform ]]; then
126        echo "Ethos-U core platform already installed. If you wish to install a new version, please delete the old folder."
127    else
128        git clone https://review.mlplatform.org/ml/ethos-u/ethos-u-core-platform
129    fi
130
131    echo "++ Setting up python environment"
132    if [[ -d ${WORKING_DIR}/cmsis_nn_venv ]]; then
133        echo "Python venv already installed. If you wish to install a new version, please delete the old folder."
134    else
135        python3 -m venv cmsis_nn_venv
136        source cmsis_nn_venv/bin/activate
137        pip3 install -r ../requirements.txt
138        deactivate
139    fi
140}
141
142Build_Tests() {
143    set -e
144    echo "++ Building Tests"
145    if [[ ${QUIET} -eq 0 ]]; then
146        cmake -S ./ -B build-${cpu}-${compiler} -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE} -DTARGET_CPU=${cpu} -DCMSIS_PATH=${CMSIS_5_PATH} -DCMSIS_OPTIMIZATION_LEVEL=${OPTIMIZATION}
147        cmake --build build-${cpu}-${compiler}/
148
149        echo "Built successfully into build-${cpu}-${compiler}"
150    else
151        cmake_command=$(cmake -S ./ -B build-${cpu}-${compiler} -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE} -DTARGET_CPU=${cpu} -DCMSIS_PATH=${CMSIS_5_PATH} -DCMSIS_OPTIMIZATION_LEVEL=${OPTIMIZATION} 2>&1)
152        make_command=$(cmake --build build-${cpu}-${compiler}/ 2>&1)
153        echo "${cmake_command}" > build-${cpu}-${compiler}/cmake_command.txt
154        echo "${make_command}" > build-${cpu}-${compiler}/make_command.txt
155
156        echo "Built successfully into build-${cpu}-${compiler}"
157    fi
158}
159
160Run_Tests() {
161    set +e
162    echo "++ Running Unit Tests"
163    readarray -d '' tests < <(find ./build-${cpu}-${compiler}/ -iname "*.elf" -print0)
164    for test in "${tests[@]}"
165    do
166        echo "Test: ${test}"
167        output=$(FVP_Corstone_SSE-300_Ethos-U55 -C mps3_board.uart0.shutdown_on_eot=1 -C mps3_board.visualisation.disable-visualisation=1 -C mps3_board.telnetterminal0.start_telnet=0 -C mps3_board.uart0.out_file="-" -C mps3_board.uart0.unbuffered_output=1 ${test})
168        echo "$output" | grep "0 Failures" -vqz
169        if [[ $? -eq 0 ]]; then
170            echo "${output}"
171            echo "${test} failed. Script exiting."
172            exit 1
173        elif [[ ${QUIET} -eq 0 ]]; then
174            echo "${output}"
175        fi
176    done
177    echo "Tests for ${cpu} ran successfully."
178}
179
180if [[ ${BUILD} -eq 0 && ${RUN} -eq 0 && ${SETUP_ENVIRONMENT} -eq 0 ]]; then
181    echo "All script functions are disabled. Script will exit and do nothing."
182    exit 1
183fi
184
185UNAME_M=$(uname -m)
186UNAME_S=$(uname -s)
187
188if [[ ${UNAME_S} != Linux ]]; then
189    echo "Error: This script only supports Linux."
190    exit 1
191fi
192
193mkdir -p downloads
194pushd downloads
195WORKING_DIR=$(pwd)
196
197if [[ ${SETUP_ENVIRONMENT} -eq 1 ]]; then
198    echo "++ Setting Environment"
199    Setup_Environment
200fi
201
202if [[ ${USE_PYTHON_VENV} -eq 1 ]]; then
203    source cmsis_nn_venv/bin/activate
204fi
205
206if [[ -z "${ETHOS_U_CORE_PLATFORM_PATH}" ]]; then
207    ETHOS_U_CORE_PLATFORM_PATH="${WORKING_DIR}/ethos-u-core-platform"
208fi
209
210if [[ -z "${CMSIS_5_PATH}" ]]; then
211    CMSIS_5_PATH="${WORKING_DIR}/CMSIS_5"
212fi
213
214popd
215IFS=',' read -r -a cpu_array <<< "$CPU"
216
217if [[ ${BUILD} -eq 1 || ${RUN} -eq 1 ]]; then
218    for cpu in "${cpu_array[@]}"
219    do
220        echo "++ Targetting ${cpu}"
221        if [[ ${USE_ARM_COMPILER} -eq 1 ]]; then
222            compiler="arm-compiler"
223            TOOLCHAIN_FILE=${ETHOS_U_CORE_PLATFORM_PATH}/cmake/toolchain/armclang.cmake
224        else
225            if [[ ${USE_GCC_FROM_DOWNLOAD} -eq 1 ]]; then
226                export PATH=${WORKING_DIR}/arm_gcc_download/bin/:${PATH}
227            fi
228            compiler="gcc"
229            TOOLCHAIN_FILE=${ETHOS_U_CORE_PLATFORM_PATH}/cmake/toolchain/arm-none-eabi-gcc.cmake
230        fi
231
232        if [[ $USE_FVP_FROM_DOWNLOAD -eq 1 ]]; then
233            if [[ ${UNAME_M} == x86_64 ]]; then
234                export PATH=${WORKING_DIR}/corstone300_download/models/Linux64_GCC-9.3/:${PATH}
235            elif [[ ${UNAME_M} == aarch64 ]]; then
236                export PATH=${WORKING_DIR}/corstone300_download/models/Linux64_armv8l_GCC-9.3/:${PATH}
237            fi
238        fi
239
240        if [[ ${BUILD} -eq 1 ]]; then
241            Build_Tests
242        fi
243
244        if [[ ${RUN} -eq 1 ]]; then
245            Run_Tests
246        fi
247    done
248fi
249
250echo ""
251echo "++ Tests for ${CPU} ran successfully"
252if [[ ${USE_PYTHON_VENV} -eq 1 ]]; then
253    deactivate
254fi