Adding CTest script

Adding script to run CTest on the Corstone-300 FVP. The script will
detect which version of the FVP that is used and pass the correct
command line arguments.

Updating README with instructions how to run tests, either using
CTest or calling the FVP directly.

Update FreeRTOS example to exit with the status of the inference.
Minor adjustments are needed to the output vectors.

Change-Id: I8a1a740b0dec2ce35d95e5c5d91f4b57a2c8e1fa
diff --git a/README.md b/README.md
index 73a1fb5..5dd5090 100644
--- a/README.md
+++ b/README.md
@@ -28,23 +28,32 @@
 
 ```
 $ cmake -B build/corstone-300 targets/corstone-300
-$ make -C build/corstone-300
+$ cd build/corstone-300
+$ make
 ```
 
 It is also possible to build with a different toolchain.
 
 ```
 $ cmake -B build/corstone-300 targets/corstone-300 -DCMAKE_TOOLCHAIN_FILE=$PWD/cmake/toolchain/arm-none-eabi-gcc.cmake
-$ make -C build/corstone-300
+$ cd build/corstone-300
+$ make
 ```
 
 ### Testing
 
 Assuming that the Corstone-300 FVP has been downloaded, installed and placed in
-the PATH variable. Then the software binary can be tested like this.
+the PATH variable. Then the software binaries can be tested like this.
 
 ```
-$ FVP_Corstone_SSE-300_Ethos-U55 build/corstone-300/ethosu_corstone_300.elf
+$ ctest
+```
+
+Individual applications can also be run directly with the FVP, for example like
+this.
+
+```
+$ FVP_Corstone_SSE-300_Ethos-U55 applications/freertos/freertos.elf
 ```
 
 # License
diff --git a/applications/freertos/main.cpp b/applications/freertos/main.cpp
index 235936e..4e58646 100644
--- a/applications/freertos/main.cpp
+++ b/applications/freertos/main.cpp
@@ -143,7 +143,7 @@
     xQueueReceive(senderQueue, &j, portMAX_DELAY);
     printf("Received inference job response. status=%u\n", j->status);
 
-    exit(0);
+    exit(j->status);
 }
 
 } // namespace
diff --git a/applications/freertos/output.h b/applications/freertos/output.h
index d5b87eb..48380b4 100644
--- a/applications/freertos/output.h
+++ b/applications/freertos/output.h
@@ -46,7 +46,7 @@
   0x42, 0x3f, 0x49, 0x3e, 0x4d, 0x4a, 0x48, 0x48, 0x3e, 0x41, 0x34, 0x39,
   0x34, 0x41, 0x35, 0x39, 0x59, 0x4f, 0x3f, 0x40, 0x42, 0x32, 0x3d, 0x34,
   0x3a, 0x34, 0x34, 0x33, 0x2e, 0x3a, 0x2e, 0x28, 0x28, 0x3b, 0x33, 0x33,
-  0x32, 0x32, 0x32, 0x2d, 0x2b, 0x37, 0x3c, 0x39, 0x28, 0x39, 0x3a, 0x32,
+  0x32, 0x32, 0x32, 0x2d, 0x2b, 0x37, 0x3c, 0x39, 0x28, 0x39, 0x39, 0x32,
   0x39, 0x2b, 0x3d, 0x41, 0x44, 0x2d, 0x35, 0x45, 0x47, 0x2e, 0x2a, 0x37,
   0x34, 0x34, 0x3c, 0x34, 0x30, 0x28, 0x27, 0x39, 0x2d, 0x34, 0x35, 0x2e,
   0x2f, 0x2a, 0x3e, 0x3c, 0x2c, 0x2a, 0x3d, 0x45, 0x48, 0x35, 0x3e, 0x29,
@@ -99,6 +99,6 @@
   0x37, 0x47, 0x48, 0x45, 0x43, 0x44, 0x42, 0x42, 0x39, 0x37, 0x2d, 0x52,
   0x38, 0x2d, 0x41, 0x32, 0x29, 0x30, 0x1f, 0x54, 0x41, 0x53, 0x3e, 0x2b,
   0x74, 0x52, 0x52, 0x45, 0x41, 0x45, 0x4f, 0x53, 0x3a, 0x58, 0x4f, 0x32,
-  0x53, 0x58, 0x50, 0x38, 0x3f, 0x40, 0x3d, 0x34, 0x43, 0x39, 0x3a, 0x26,
+  0x53, 0x58, 0x50, 0x38, 0x3f, 0x40, 0x3d, 0x34, 0x43, 0x39, 0x39, 0x26,
   0x2d, 0x3b, 0x2b, 0x37, 0x63
 };
diff --git a/applications/trustzone_inference/CMakeLists.txt b/applications/trustzone_inference/CMakeLists.txt
index 2caf983..f23c7f6 100644
--- a/applications/trustzone_inference/CMakeLists.txt
+++ b/applications/trustzone_inference/CMakeLists.txt
@@ -36,4 +36,4 @@
 
 ethosu_add_test(trustzone_secure
     NAME trustzone
-    COMMAND ${ETHOSU_COMMAND_DEFAULT} --data ${CMAKE_CURRENT_BINARY_DIR}/nonsecure/ns_bin@0x7c000)
+    COMMAND ${ETHOSU_COMMAND_DEFAULT} -- --data ${CMAKE_CURRENT_BINARY_DIR}/nonsecure/ns_bin@0x7c000)
diff --git a/applications/trustzone_inference/secure/output.h b/applications/trustzone_inference/secure/output.h
index d5b87eb..48380b4 100644
--- a/applications/trustzone_inference/secure/output.h
+++ b/applications/trustzone_inference/secure/output.h
@@ -46,7 +46,7 @@
   0x42, 0x3f, 0x49, 0x3e, 0x4d, 0x4a, 0x48, 0x48, 0x3e, 0x41, 0x34, 0x39,
   0x34, 0x41, 0x35, 0x39, 0x59, 0x4f, 0x3f, 0x40, 0x42, 0x32, 0x3d, 0x34,
   0x3a, 0x34, 0x34, 0x33, 0x2e, 0x3a, 0x2e, 0x28, 0x28, 0x3b, 0x33, 0x33,
-  0x32, 0x32, 0x32, 0x2d, 0x2b, 0x37, 0x3c, 0x39, 0x28, 0x39, 0x3a, 0x32,
+  0x32, 0x32, 0x32, 0x2d, 0x2b, 0x37, 0x3c, 0x39, 0x28, 0x39, 0x39, 0x32,
   0x39, 0x2b, 0x3d, 0x41, 0x44, 0x2d, 0x35, 0x45, 0x47, 0x2e, 0x2a, 0x37,
   0x34, 0x34, 0x3c, 0x34, 0x30, 0x28, 0x27, 0x39, 0x2d, 0x34, 0x35, 0x2e,
   0x2f, 0x2a, 0x3e, 0x3c, 0x2c, 0x2a, 0x3d, 0x45, 0x48, 0x35, 0x3e, 0x29,
@@ -99,6 +99,6 @@
   0x37, 0x47, 0x48, 0x45, 0x43, 0x44, 0x42, 0x42, 0x39, 0x37, 0x2d, 0x52,
   0x38, 0x2d, 0x41, 0x32, 0x29, 0x30, 0x1f, 0x54, 0x41, 0x53, 0x3e, 0x2b,
   0x74, 0x52, 0x52, 0x45, 0x41, 0x45, 0x4f, 0x53, 0x3a, 0x58, 0x4f, 0x32,
-  0x53, 0x58, 0x50, 0x38, 0x3f, 0x40, 0x3d, 0x34, 0x43, 0x39, 0x3a, 0x26,
+  0x53, 0x58, 0x50, 0x38, 0x3f, 0x40, 0x3d, 0x34, 0x43, 0x39, 0x39, 0x26,
   0x2d, 0x3b, 0x2b, 0x37, 0x63
 };
diff --git a/scripts/run_ctest.py b/scripts/run_ctest.py
new file mode 100755
index 0000000..3fabe44
--- /dev/null
+++ b/scripts/run_ctest.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python3
+
+#
+# Copyright (c) 2021 Arm Limited. All rights reserved.
+#
+# 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.
+#
+
+import argparse
+import subprocess
+import sys
+
+def __print_arguments(args):
+    if isinstance(args, list):
+        print("$ " + " ".join(args))
+    else:
+        print(args)
+
+def Popen(args, **kwargs):
+    __print_arguments(args)
+    return subprocess.Popen(args, **kwargs)
+
+def call(args, **kwargs):
+    __print_arguments(args)
+    return subprocess.call(args, **kwargs)
+
+def check_call(args, **kwargs):
+    __print_arguments(args)
+    return subprocess.check_call(args, **kwargs)
+
+def check_output(args, **kwargs):
+    __print_arguments(args)
+    return subprocess.check_output(args, **kwargs)
+
+def run_corstone_300(args):
+    # Verify supported FVP version
+    version = subprocess.check_output(['FVP_Corstone_SSE-300_Ethos-U55', '--version']).decode()
+    supported_version = ['11.13']
+
+    if not [s for s in supported_version if s in version]:
+        raise Exception("Incorrect FVP version. Supported versions are '{}'.".format(supported_version))
+
+    # FVP executable
+    cmd = ['FVP_Corstone_SSE-300_Ethos-U55']
+
+    # NPU configuration
+    cmd += ['-C', 'ethosu.num_macs=' + str(args.macs)]
+
+    # Output parameters
+    cmd += ['-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',
+            '-C', 'mps3_board.uart0.shutdown_tag="EXITTHESIM"']
+
+    cmd += args.args
+
+    # Run FVP and tee output to console while scanning for exit tag
+    ret = 1
+    proc = Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    while True:
+        line = proc.stdout.readline().decode()
+        if not line:
+                break
+
+        if 'Application exit code: 0.' in line:
+            ret = 0
+
+        sys.stdout.write(line)
+        sys.stdout.flush()
+
+    return ret
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description='Run a test with given test command and test binary.')
+    parser.add_argument('-t', '--target', choices=['corstone-300'], required=True, help='FVP target.')
+    parser.add_argument('-a', '--arch', choices=['ethos-u55'], default='ethos-u55', help='NPU architecture.')
+    parser.add_argument('-m', '--macs', type=int, choices=[32, 64, 128, 256], default=128, help='NPU number of MACs.')
+    parser.add_argument('args', nargs='+', help='Arguments.')
+    args = parser.parse_args()
+
+    if args.target == 'corstone-300':
+        sys.exit(run_corstone_300(args))
diff --git a/targets/corstone-300/CMakeLists.txt b/targets/corstone-300/CMakeLists.txt
index 2efd329..d41a7d3 100644
--- a/targets/corstone-300/CMakeLists.txt
+++ b/targets/corstone-300/CMakeLists.txt
@@ -26,32 +26,7 @@
     set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/toolchain/armclang.cmake")
 endif()
 
-execute_process(
-    COMMAND FVP_Corstone_SSE-300_Ethos-U55 --version
-    OUTPUT_VARIABLE FVP_VERSION_OUTPUT
-    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
-    COMMAND_ERROR_IS_FATAL ANY)
-
-if (${FVP_VERSION_OUTPUT} MATCHES "Fast Models \\[([0-9]+\\.[0-9]+\\.[0-9]+)")
-    set(FVP_VERSION ${CMAKE_MATCH_1})
-else()
-    message("Unknown version")
-endif()
-
-if (${FVP_VERSION} STREQUAL "11.13.41")
-    set(FVP_EXTRA_ARGS "ethosu.num_macs=128")
-elseif (${FVP_VERSION} STREQUAL "11.12.57")
-    set(FVP_EXTRA_ARGS "ethosu.config=H128")
-endif ()
-
-set(ETHOSU_COMMAND_DEFAULT
-    FVP_Corstone_SSE-300_Ethos-U55
-        -C ${FVP_EXTRA_ARGS}
-        -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
-        -C mps3_board.uart0.shutdown_tag="EXITTHESIM")
+set(ETHOSU_COMMAND_DEFAULT ${CMAKE_CURRENT_SOURCE_DIR}/../../scripts/run_ctest.py -t corstone-300)
 
 #############################################################################
 # Project