MLECO-4503: Adding video VSI for object detection

* Added Board support - Arm Corstone 300 and 310
* Added Python Scripts for Video VSI
* Added source files for Video VSI
* Add new usecase handler for OD use case
* Bumped resampy version to resolve issue with slowdown

Signed-off-by: Idriss Chaouch <idriss.chaouch@arm.com>

Signed-off-by: Richard Burton <richard.burton@arm.com>

Change-Id: Ie59ae955d4d85f672a49c63733052624542aec85
diff --git a/.pylintrc b/.pylintrc
index b6cb7ee..2f339b1 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -1,4 +1,4 @@
-#  SPDX-FileCopyrightText:  Copyright 2023 Arm Limited and/or its affiliates <open-source-office@arm.com>
+#  SPDX-FileCopyrightText:  Copyright 2023-2024 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");
@@ -67,7 +67,9 @@
 # ignore-list. The regex matches against paths and can be in Posix or Windows
 # format. Because '\\' represents the directory delimiter on Windows systems,
 # it can't be used as an escape character.
-ignore-paths=
+ignore-paths=^dependencies/.*$,
+             ^resources_downloaded/.*$,
+             ^scripts/py/vsi/.*$
 
 # Files or directories matching the regular expression patterns are skipped.
 # The regex matches against base names, not paths. The default value ignores
diff --git a/build_default.py b/build_default.py
index 907bf4d..8b7e1ef 100755
--- a/build_default.py
+++ b/build_default.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python3
-#  SPDX-FileCopyrightText:  Copyright 2021-2023 Arm Limited and/or its affiliates <open-source-office@arm.com>
+#  SPDX-FileCopyrightText:  Copyright 2021-2024 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");
@@ -186,7 +186,7 @@
                             - toolchain
                             - download_resources
                             - run_vela_on_models
-                            - np_config_name
+                            - npu_config_name
     toolchain (str)             :   Specifies if 'gnu' or 'arm' toolchain needs to be used.
     download_resources (bool)   :   Specifies if 'Download resources' step is performed.
     run_vela_on_models (bool)   :   Only if `download_resources` is True, specifies if
diff --git a/docs/sections/building.md b/docs/sections/building.md
index d20e570..dcb6e21 100644
--- a/docs/sections/building.md
+++ b/docs/sections/building.md
@@ -917,10 +917,12 @@
 
 ```c++
 # Generate input files
-generate_images_code("${${use_case}_FILE_PATH}"
-                     ${SRC_GEN_DIR}
-                     ${INC_GEN_DIR}
-                     "${${use_case}_IMAGE_SIZE}")
+generate_images_code(
+    INPUT_DIR   "${${use_case}_FILE_PATH}"
+    SRC_OUT     ${SRC_GEN_DIR}
+    HDR_OUT     ${INC_GEN_DIR}
+    IMG_SIZE    "${${use_case}_IMAGE_SIZE}"
+ )
 
 # Generate labels file
 set(${use_case}_LABELS_CPP_FILE Labels)
diff --git a/docs/use_cases/object_detection.md b/docs/use_cases/object_detection.md
index e946c1b..583a8e5 100644
--- a/docs/use_cases/object_detection.md
+++ b/docs/use_cases/object_detection.md
@@ -6,6 +6,7 @@
   - [Building the code sample application from sources](./object_detection.md#building-the-code-sample-application-from-sources)
     - [Build options](./object_detection.md#build-options)
     - [Build process](./object_detection.md#build-process)
+    - [Build with VSI support](./object_detection.md#build-with-vsi-support)
     - [Add custom input](./object_detection.md#add-custom-input)
     - [Add custom model](./object_detection.md#add-custom-model)
   - [Setting up and running Ethos-U NPU code sample](./object_detection.md#setting-up-and-running-ethos_u-npu-code-sample)
@@ -64,6 +65,14 @@
 - `object_detection_ACTIVATION_BUF_SZ`: The intermediate, or activation, buffer size reserved for the NN model.
   By default, it is set to 2MiB and is enough for most models.
 
+- `VSI_ENABLED`: Build the application with support for the [Virtual Streaming Interface (VSI)](https://arm-software.github.io/AVH/main/simulation/html/group__arm__vsi.html)
+  available on the Arm® Corstone™-300 and Arm® Corstone™-310 FVPs.
+  This adds the option to run the application using frames consumed from the host's webcam as input in place of static images.
+
+- `VSI_IMAGE_INPUT`: When used with the `VSI_ENABLED` flag, the VSI option in the application will consume images from the
+  filesystem specified by the `object_detection_FILE_PATH` path over VSI instead of consuming frames from the host's webcam.
+  This can be useful for automated testing.
+
 To build **ONLY** the Object Detection example application, add `-DUSE_CASE_BUILD=object_detection` to the `cmake` command
 line, as specified in: [Building](../documentation.md#Building).
 
@@ -142,6 +151,31 @@
 - `Images-object_detection.txt`: Tells the FPGA which memory regions to use for loading the binaries
   in the `sectors/...` folder.
 
+### Build with VSI support
+
+The Object Detection use case can be compiled to consume input from the
+[Virtual Streaming Interface (VSI)](https://arm-software.github.io/AVH/main/simulation/html/group__arm__vsi.html)
+available on the Arm® Corstone™-300 and Arm® Corstone™-310 FVPs.
+
+By default, this consumes frames from the webcam attached to the host which are then used to perform face detection.
+
+To build the use case with VSI support, supply the additional argument to CMake:
+
+```commandline
+-DVSI_ENABLED=1
+```
+
+For testing purposes, it can be useful to specify still images to be consumed over VSI instead of the webcam feed.
+To do this, supply the following arguments to CMake:
+
+```commandline
+-DVSI_ENABLED=1
+-DVSI_IMAGE_INPUT=1
+```
+
+When `VSI_IMAGE_INPUT` is set, images will be read from the default location.
+This can be overriden - see the [Add custom input](./object_detection.md#add-custom-input) section below.
+
 ### Add custom input
 
 The application object detection is set up to perform inferences on data found in the folder, or an individual file,
@@ -259,6 +293,27 @@
 ~/FVP_install_location/models/Linux64_GCC-6.4/FVP_Corstone_SSE-300_Ethos-U55 ./bin/mps3-sse-300/ethos-u-object_detection.axf
 ```
 
+If the application has been built with VSI support, additional arguments are needed:
+
+```commandline
+~/FVP_install_location/models/Linux64_GCC-9.3/FVP_Corstone_SSE-300_Ethos-U55 \
+    -a ./bin/ethos-u-object_detection.axf \
+    -C mps3_board.v_path=./scripts/py/vsi
+```
+
+Note that VSI support is available in 11.22.35 of the FVP or above.  Run the following to check the version:
+
+```log
+./FVP_Corstone_SSE-300_Ethos-U65 --version
+
+Fast Models [11.22.35 (Aug 18 2023)]
+Copyright 2000-2023 ARM Limited.
+All Rights Reserved.
+
+
+Info: /OSCI/SystemC: Simulation stopped by user.
+```
+
 A log output appears on the terminal:
 
 ```log
@@ -290,6 +345,23 @@
 
 ```
 
+If `VSI_ENABLED` has been set, a sixth option will appear:
+
+```log
+User input required
+Enter option number from:
+
+  1. Run detection on next ifm
+  2. Run detection ifm at chosen index
+  3. Run detection on all ifm
+  4. Show NN model info
+  5. List ifm
+  6. Run detection using VSI as input
+
+Choice:
+
+```
+
 What the preceding choices do:
 
 1. Run detection on next ifm: Runs a single inference on the next in line image from the collection of the compiled images.
@@ -351,6 +423,15 @@
     INFO - 3 => pitch_and_roll.bmp
     ```
 
+6. Run detection using VSI as input: this will begin to consume frames from the webcam connected to the host.
+   Preprocessing and inference will be run on each frame, which will then be displayed with a bounding box around any
+   detected face.  The next frame will then be consumed and the process will repeat.
+
+   Alternatively, if `VSI_IMAGE_INPUT` has been passed, the behaviour of this option will be similar to that of
+   option 1, with inference performed on a set of pre-defined images.
+   However, these images will be read from the local filesystem over VSI at runtime,
+   whereas option 1 continues to use images that have been pre-compiled into the application.
+
 ### Running Object Detection
 
 Please select the first menu option to execute Object Detection.
diff --git a/scripts/cmake/common_user_options.cmake b/scripts/cmake/common_user_options.cmake
index 97e9b40..64b9c74 100644
--- a/scripts/cmake/common_user_options.cmake
+++ b/scripts/cmake/common_user_options.cmake
@@ -1,5 +1,5 @@
 #----------------------------------------------------------------------------
-#  SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+#  SPDX-FileCopyrightText: Copyright 2021-2022, 2024 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");
@@ -81,6 +81,21 @@
     OFF
     BOOL)
 
+if (TARGET_PLATFORM STREQUAL mps3)
+    USER_OPTION(VSI_ENABLED
+        "Select to use VSI for video streaming"
+        OFF
+        BOOL)
+
+    if (VSI_ENABLED)
+        USER_OPTION(VSI_IMAGE_INPUT
+            "Use images as input to VSI instead of video"
+            OFF
+            BOOL
+        )
+    endif ()
+endif ()
+
 if (NOT TARGET_PLATFORM STREQUAL native)
 
     USER_OPTION(CMSIS_SRC_PATH
diff --git a/scripts/cmake/platforms/mps3/sse-310/mps3-sse-310.sct b/scripts/cmake/platforms/mps3/sse-310/mps3-sse-310.sct
index c49f628..691e6ee 100644
--- a/scripts/cmake/platforms/mps3/sse-310/mps3-sse-310.sct
+++ b/scripts/cmake/platforms/mps3/sse-310/mps3-sse-310.sct
@@ -1,4 +1,4 @@
-;  SPDX-FileCopyrightText: Copyright 2021, 2023 Arm Limited and/or its affiliates <open-source-office@arm.com>
+;  SPDX-FileCopyrightText: Copyright 2021, 2023-2024 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");
@@ -42,7 +42,7 @@
         * (InRoot$$Sections)
 
         ; Essentially only RO (code + data)
-        .ANY (+RO)
+        .ANY (+RO +RO-DATA)
     }
 
     ;-----------------------------------------------------
@@ -51,9 +51,6 @@
     ;-----------------------------------------------------
     data.bin        0x110A0000 ALIGN 8          0x00060000
     {
-        ; Any RO-DATA
-        .ANY (+RO-DATA)
-
         ; Any R/W and/or zero initialised data
         .ANY(+RW +ZI)
     }
diff --git a/scripts/cmake/source_gen_utils.cmake b/scripts/cmake/source_gen_utils.cmake
index 6287cb6..6d65049 100644
--- a/scripts/cmake/source_gen_utils.cmake
+++ b/scripts/cmake/source_gen_utils.cmake
@@ -1,5 +1,5 @@
 #----------------------------------------------------------------------------
-#  SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates <open-source-office@arm.com>
+#  SPDX-FileCopyrightText: Copyright 2021, 2024 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");
@@ -20,12 +20,21 @@
 # This function generates C++ files for images located in the directory it is
 # pointed at. NOTE: uses python
 ##############################################################################
-function(generate_images_code input_dir src_out hdr_out img_size)
+function(generate_images_code)
+    set(options GENERATE_FILE_PATHS)
+    set(oneValueArgs INPUT_DIR SRC_OUT HDR_OUT IMG_SIZE)
+    cmake_parse_arguments(PARSED "${options}" "${oneValueArgs}" "" ${ARGN})
 
     # Absolute paths for passing into python script
-    get_filename_component(input_dir_abs ${input_dir} ABSOLUTE)
-    get_filename_component(src_out_abs ${src_out} ABSOLUTE)
-    get_filename_component(hdr_out_abs ${hdr_out} ABSOLUTE)
+    get_filename_component(input_dir_abs ${PARSED_INPUT_DIR} ABSOLUTE)
+    get_filename_component(src_out_abs ${PARSED_SRC_OUT} ABSOLUTE)
+    get_filename_component(hdr_out_abs ${PARSED_HDR_OUT} ABSOLUTE)
+
+    if (${PARSED_GENERATE_FILE_PATHS})
+        set(GENERATE_FILE_PATHS_ARG "--generate_file_paths")
+    else ()
+        set(GENERATE_FILE_PATHS_ARG "")
+    endif ()
 
     message(STATUS "Generating image files from ${input_dir_abs}")
     execute_process(
@@ -33,7 +42,8 @@
         --image_path ${input_dir_abs}
         --source_folder_path ${src_out_abs}
         --header_folder_path ${hdr_out_abs}
-        --image_size ${img_size} ${img_size}
+        --image_size ${PARSED_IMG_SIZE} ${PARSED_IMG_SIZE}
+        ${GENERATE_FILE_PATHS_ARG}
         RESULT_VARIABLE return_code
     )
     if (NOT return_code EQUAL "0")
diff --git a/scripts/py/gen_rgb_cpp.py b/scripts/py/gen_rgb_cpp.py
index e1c93bb..f1200e6 100644
--- a/scripts/py/gen_rgb_cpp.py
+++ b/scripts/py/gen_rgb_cpp.py
@@ -1,4 +1,4 @@
-#  SPDX-FileCopyrightText:  Copyright 2021-2023 Arm Limited and/or its affiliates <open-source-office@arm.com>
+#  SPDX-FileCopyrightText:  Copyright 2021-2024 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");
@@ -18,6 +18,7 @@
 corresponding cpp files and a single hpp file referencing the vectors
 from the cpp files.
 """
+import argparse
 import glob
 import math
 import typing
@@ -60,6 +61,13 @@
 )
 
 parser.add_argument(
+    "--generate_file_paths",
+    type=bool,
+    action=argparse.BooleanOptionalAction,
+    help="Generate an array of file paths to the images as well as the images themselves."
+)
+
+parser.add_argument(
     "--license_template",
     type=str,
     help="Header template file",
@@ -85,11 +93,12 @@
     image_filenames: typing.List[str]
 
 
-def write_hpp_file(
+def write_metadata_files(
         images_params: ImagesParams,
         header_file_path: Path,
         cc_file_path: Path,
         header_template_file: str,
+        source_directory: str = None
 ):
     """
     Write Images.hpp and Images.cc
@@ -98,6 +107,7 @@
     @param header_file_path:        Images.hpp path
     @param cc_file_path:            Images.cc path
     @param header_template_file:    Header template file name
+    @param source_directory:        Optional source directory of images
     """
     print(f"++ Generating {header_file_path}")
     hdr = GenUtils.gen_header(env, header_template_file)
@@ -109,14 +119,16 @@
         .stream(common_template_header=hdr,
                 imgs_count=images_params.num_images,
                 img_size=image_size,
-                var_names=images_params.image_array_names) \
+                var_names=images_params.image_array_names,
+                source_directory=source_directory) \
         .dump(str(header_file_path))
 
     env \
         .get_template('Images.cc.template') \
         .stream(common_template_header=hdr,
                 var_names=images_params.image_array_names,
-                img_names=images_params.image_filenames) \
+                img_names=images_params.image_filenames,
+                source_directory=source_directory) \
         .dump(str(cc_file_path))
 
 
@@ -196,9 +208,13 @@
     image_filenames = []
     image_array_names = []
 
-    if Path(args.image_path).is_dir():
+    image_path = Path(args.image_path)
+
+    if image_path.is_dir():
+        image_directory = image_path
         filepaths = sorted(glob.glob(str(Path(args.image_path) / '**/*.*'), recursive=True))
-    elif Path(args.image_path).is_file():
+    elif image_path.is_file():
+        image_directory = image_path.parent
         filepaths = [args.image_path]
     else:
         raise OSError("Directory or file does not exist.")
@@ -228,13 +244,16 @@
         # Increment image index
         image_idx = image_idx + 1
 
-    header_filepath = Path(args.header_folder_path) / "InputFiles.hpp"
-    common_cc_filepath = Path(args.source_folder_path) / "InputFiles.cc"
-
-    images_params = ImagesParams(image_idx, args.image_size, image_array_names, image_filenames)
-
     if len(image_filenames) > 0:
-        write_hpp_file(images_params, header_filepath, common_cc_filepath, args.license_template)
+        images_params = ImagesParams(image_idx, args.image_size, image_array_names, image_filenames)
+
+        write_metadata_files(
+            images_params,
+            header_file_path=Path(args.header_folder_path) / "InputFiles.hpp",
+            cc_file_path=Path(args.source_folder_path) / "InputFiles.cc",
+            header_template_file=args.license_template,
+            source_directory=image_directory if args.generate_file_paths else None
+        )
     else:
         raise FileNotFoundError("No valid images found.")
 
diff --git a/scripts/py/templates/Images.cc.template b/scripts/py/templates/Images.cc.template
index 2620ab4..c5b051a 100644
--- a/scripts/py/templates/Images.cc.template
+++ b/scripts/py/templates/Images.cc.template
@@ -1,5 +1,5 @@
 {#
- SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates <open-source-office@arm.com>
+ SPDX-FileCopyrightText: Copyright 2021, 2024 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");
@@ -24,6 +24,14 @@
 {% endfor %}
 };
 
+{% if source_directory %}
+static const char* imgFilePaths[] = {
+{% for name in img_names %}
+    "{{source_directory}}/{{name}}",
+{% endfor %}
+};
+{% endif %}
+
 static const uint8_t* imgArrays[] = {
     {{ var_names|join(',\n    ') }}
 };
@@ -36,6 +44,16 @@
     return nullptr;
 }
 
+{% if source_directory %}
+const char* GetFilePath(const uint32_t idx)
+{
+    if (idx < NUMBER_OF_FILES) {
+        return imgFilePaths[idx];
+    }
+    return nullptr;
+}
+{% endif %}
+
 const uint8_t* GetImgArray(const uint32_t idx)
 {
     if (idx < NUMBER_OF_FILES) {
diff --git a/scripts/py/templates/Images.hpp.template b/scripts/py/templates/Images.hpp.template
index d39fc49..1f0a70e 100644
--- a/scripts/py/templates/Images.hpp.template
+++ b/scripts/py/templates/Images.hpp.template
@@ -1,5 +1,5 @@
 {#
- SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates <open-source-office@arm.com>
+ SPDX-FileCopyrightText: Copyright 2021, 2024 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");
@@ -35,6 +35,15 @@
  **/
 const char* GetFilename(const uint32_t idx);
 
+{% if source_directory %}
+/**
+ * @brief       Gets the file path for the image on the local filesystem
+ * @param[in]   idx     Index of the input.
+ * @return      const C string pointer to the file path.
+ **/
+const char* GetFilePath(const uint32_t idx);
+{% endif %}
+
 /**
  * @brief       Gets the pointer to image data.
  * @param[in]   idx     Index of the input.
diff --git a/scripts/py/vsi/arm_vsi4.py b/scripts/py/vsi/arm_vsi4.py
new file mode 100644
index 0000000..c903ab2
--- /dev/null
+++ b/scripts/py/vsi/arm_vsi4.py
@@ -0,0 +1,207 @@
+#!/usr/bin/env python3
+#  SPDX-FileCopyrightText:  Copyright 2024 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
+#
+#      http://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 logging
+import vsi_video
+
+## Set verbosity level
+#verbosity = logging.DEBUG
+verbosity = logging.ERROR
+
+# [debugging] Verbosity settings
+level = { 10: "DEBUG",  20: "INFO",  30: "WARNING",  40: "ERROR" }
+logging.basicConfig(format='Py: VSI4: [%(levelname)s]\t%(message)s', level = verbosity)
+logging.info("Verbosity level is set to " + level[verbosity])
+
+
+# Video Server configuration
+server_address = ('127.0.0.1', 6000)
+server_authkey = 'vsi_video'
+
+
+# IRQ registers
+IRQ_Status = 0
+
+# Timer registers
+Timer_Control  = 0
+Timer_Interval = 0
+
+# Timer Control register definitions
+Timer_Control_Run_Msk      = 1<<0
+Timer_Control_Periodic_Msk = 1<<1
+Timer_Control_Trig_IRQ_Msk = 1<<2
+Timer_Control_Trig_DMA_Msk = 1<<3
+
+# DMA registers
+DMA_Control = 0
+
+# DMA Control register definitions
+DMA_Control_Enable_Msk    = 1<<0
+DMA_Control_Direction_Msk = 1<<1
+DMA_Control_Direction_P2M = 0<<1
+DMA_Control_Direction_M2P = 1<<1
+
+# User registers
+Regs = [0] * 64
+
+# Data buffer
+Data = bytearray()
+
+
+## Initialize
+#  @return None
+def init():
+    logging.info("Python function init() called")
+    vsi_video.init(server_address, server_authkey)
+
+
+## Read interrupt request (the VSI IRQ Status Register)
+#  @return value value read (32-bit)
+def rdIRQ():
+    global IRQ_Status
+    logging.info("Python function rdIRQ() called")
+
+    value = IRQ_Status
+    logging.debug("Read interrupt request: {}".format(value))
+
+    return value
+
+
+## Write interrupt request (the VSI IRQ Status Register)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrIRQ(value):
+    global IRQ_Status
+    logging.info("Python function wrIRQ() called")
+
+    value = vsi_video.wrIRQ(IRQ_Status, value)
+    IRQ_Status = value
+    logging.debug("Write interrupt request: {}".format(value))
+
+    return value
+
+
+## Write Timer registers (the VSI Timer Registers)
+#  @param index Timer register index (zero based)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrTimer(index, value):
+    global Timer_Control, Timer_Interval
+    logging.info("Python function wrTimer() called")
+
+    if   index == 0:
+        Timer_Control = value
+        logging.debug("Write Timer_Control: {}".format(value))
+    elif index == 1:
+        Timer_Interval = value
+        logging.debug("Write Timer_Interval: {}".format(value))
+
+    return value
+
+
+## Timer event (called at Timer Overflow)
+#  @return None
+def timerEvent():
+    global IRQ_Status
+
+    logging.info("Python function timerEvent() called")
+
+    IRQ_Status = vsi_video.timerEvent(IRQ_Status)
+
+
+## Write DMA registers (the VSI DMA Registers)
+#  @param index DMA register index (zero based)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrDMA(index, value):
+    global DMA_Control
+    logging.info("Python function wrDMA() called")
+
+    if   index == 0:
+        DMA_Control = value
+        logging.debug("Write DMA_Control: {}".format(value))
+
+    return value
+
+
+## Read data from peripheral for DMA P2M transfer (VSI DMA)
+#  @param size size of data to read (in bytes, multiple of 4)
+#  @return data data read (bytearray)
+def rdDataDMA(size):
+    global Data
+    logging.info("Python function rdDataDMA() called")
+
+    Data = vsi_video.rdDataDMA(size)
+
+    n = min(len(Data), size)
+    data = bytearray(size)
+    data[0:n] = Data[0:n]
+    logging.debug("Read data ({} bytes)".format(size))
+
+    return data
+
+
+## Write data to peripheral for DMA M2P transfer (VSI DMA)
+#  @param data data to write (bytearray)
+#  @param size size of data to write (in bytes, multiple of 4)
+#  @return None
+def wrDataDMA(data, size):
+    global Data
+    logging.info("Python function wrDataDMA() called")
+
+    Data = data
+    logging.debug("Write data ({} bytes)".format(size))
+
+    vsi_video.wrDataDMA(data, size)
+
+    return
+
+
+## Read user registers (the VSI User Registers)
+#  @param index user register index (zero based)
+#  @return value value read (32-bit)
+def rdRegs(index):
+    global Regs
+    logging.info("Python function rdRegs() called")
+
+    if index <= vsi_video.REG_IDX_MAX:
+        Regs[index] = vsi_video.rdRegs(index)
+
+    value = Regs[index]
+    logging.debug("Read user register at index {}: {}".format(index, value))
+
+    return value
+
+
+## Write user registers (the VSI User Registers)
+#  @param index user register index (zero based)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrRegs(index, value):
+    global Regs
+    logging.info("Python function wrRegs() called")
+
+    if index <= vsi_video.REG_IDX_MAX:
+        value = vsi_video.wrRegs(index, value)
+
+    Regs[index] = value
+    logging.debug("Write user register at index {}: {}".format(index, value))
+
+    return value
+
+
+## @}
+
diff --git a/scripts/py/vsi/arm_vsi5.py b/scripts/py/vsi/arm_vsi5.py
new file mode 100644
index 0000000..8056096
--- /dev/null
+++ b/scripts/py/vsi/arm_vsi5.py
@@ -0,0 +1,207 @@
+#!/usr/bin/env python3
+#  SPDX-FileCopyrightText:  Copyright 2024 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
+#
+#      http://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 logging
+import vsi_video
+
+## Set verbosity level
+#verbosity = logging.DEBUG
+verbosity = logging.ERROR
+
+# [debugging] Verbosity settings
+level = { 10: "DEBUG",  20: "INFO",  30: "WARNING",  40: "ERROR" }
+logging.basicConfig(format='Py: VSI5: [%(levelname)s]\t%(message)s', level = verbosity)
+logging.info("Verbosity level is set to " + level[verbosity])
+
+
+# Video Server configuration
+server_address = ('127.0.0.1', 6001)
+server_authkey = 'vsi_video'
+
+
+# IRQ registers
+IRQ_Status = 0
+
+# Timer registers
+Timer_Control  = 0
+Timer_Interval = 0
+
+# Timer Control register definitions
+Timer_Control_Run_Msk      = 1<<0
+Timer_Control_Periodic_Msk = 1<<1
+Timer_Control_Trig_IRQ_Msk = 1<<2
+Timer_Control_Trig_DMA_Msk = 1<<3
+
+# DMA registers
+DMA_Control = 0
+
+# DMA Control register definitions
+DMA_Control_Enable_Msk    = 1<<0
+DMA_Control_Direction_Msk = 1<<1
+DMA_Control_Direction_P2M = 0<<1
+DMA_Control_Direction_M2P = 1<<1
+
+# User registers
+Regs = [0] * 64
+
+# Data buffer
+Data = bytearray()
+
+
+## Initialize
+#  @return None
+def init():
+    logging.info("Python function init() called")
+    vsi_video.init(server_address, server_authkey)
+
+
+## Read interrupt request (the VSI IRQ Status Register)
+#  @return value value read (32-bit)
+def rdIRQ():
+    global IRQ_Status
+    logging.info("Python function rdIRQ() called")
+
+    value = IRQ_Status
+    logging.debug("Read interrupt request: {}".format(value))
+
+    return value
+
+
+## Write interrupt request (the VSI IRQ Status Register)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrIRQ(value):
+    global IRQ_Status
+    logging.info("Python function wrIRQ() called")
+
+    value = vsi_video.wrIRQ(IRQ_Status, value)
+    IRQ_Status = value
+    logging.debug("Write interrupt request: {}".format(value))
+
+    return value
+
+
+## Write Timer registers (the VSI Timer Registers)
+#  @param index Timer register index (zero based)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrTimer(index, value):
+    global Timer_Control, Timer_Interval
+    logging.info("Python function wrTimer() called")
+
+    if   index == 0:
+        Timer_Control = value
+        logging.debug("Write Timer_Control: {}".format(value))
+    elif index == 1:
+        Timer_Interval = value
+        logging.debug("Write Timer_Interval: {}".format(value))
+
+    return value
+
+
+## Timer event (called at Timer Overflow)
+#  @return None
+def timerEvent():
+    global IRQ_Status
+
+    logging.info("Python function timerEvent() called")
+
+    IRQ_Status = vsi_video.timerEvent(IRQ_Status)
+
+
+## Write DMA registers (the VSI DMA Registers)
+#  @param index DMA register index (zero based)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrDMA(index, value):
+    global DMA_Control
+    logging.info("Python function wrDMA() called")
+
+    if   index == 0:
+        DMA_Control = value
+        logging.debug("Write DMA_Control: {}".format(value))
+
+    return value
+
+
+## Read data from peripheral for DMA P2M transfer (VSI DMA)
+#  @param size size of data to read (in bytes, multiple of 4)
+#  @return data data read (bytearray)
+def rdDataDMA(size):
+    global Data
+    logging.info("Python function rdDataDMA() called")
+
+    Data = vsi_video.rdDataDMA(size)
+
+    n = min(len(Data), size)
+    data = bytearray(size)
+    data[0:n] = Data[0:n]
+    logging.debug("Read data ({} bytes)".format(size))
+
+    return data
+
+
+## Write data to peripheral for DMA M2P transfer (VSI DMA)
+#  @param data data to write (bytearray)
+#  @param size size of data to write (in bytes, multiple of 4)
+#  @return None
+def wrDataDMA(data, size):
+    global Data
+    logging.info("Python function wrDataDMA() called")
+
+    Data = data
+    logging.debug("Write data ({} bytes)".format(size))
+
+    vsi_video.wrDataDMA(data, size)
+
+    return
+
+
+## Read user registers (the VSI User Registers)
+#  @param index user register index (zero based)
+#  @return value value read (32-bit)
+def rdRegs(index):
+    global Regs
+    logging.info("Python function rdRegs() called")
+
+    if index <= vsi_video.REG_IDX_MAX:
+        Regs[index] = vsi_video.rdRegs(index)
+
+    value = Regs[index]
+    logging.debug("Read user register at index {}: {}".format(index, value))
+
+    return value
+
+
+## Write user registers (the VSI User Registers)
+#  @param index user register index (zero based)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrRegs(index, value):
+    global Regs
+    logging.info("Python function wrRegs() called")
+
+    if index <= vsi_video.REG_IDX_MAX:
+        value = vsi_video.wrRegs(index, value)
+
+    Regs[index] = value
+    logging.debug("Write user register at index {}: {}".format(index, value))
+
+    return value
+
+
+## @}
+
diff --git a/scripts/py/vsi/arm_vsi6.py b/scripts/py/vsi/arm_vsi6.py
new file mode 100644
index 0000000..3d71562
--- /dev/null
+++ b/scripts/py/vsi/arm_vsi6.py
@@ -0,0 +1,207 @@
+#!/usr/bin/env python3
+#  SPDX-FileCopyrightText:  Copyright 2024 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
+#
+#      http://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 logging
+import vsi_video
+
+## Set verbosity level
+#verbosity = logging.DEBUG
+verbosity = logging.ERROR
+
+# [debugging] Verbosity settings
+level = { 10: "DEBUG",  20: "INFO",  30: "WARNING",  40: "ERROR" }
+logging.basicConfig(format='Py: VSI6: [%(levelname)s]\t%(message)s', level = verbosity)
+logging.info("Verbosity level is set to " + level[verbosity])
+
+
+# Video Server configuration
+server_address = ('127.0.0.1', 6002)
+server_authkey = 'vsi_video'
+
+
+# IRQ registers
+IRQ_Status = 0
+
+# Timer registers
+Timer_Control  = 0
+Timer_Interval = 0
+
+# Timer Control register definitions
+Timer_Control_Run_Msk      = 1<<0
+Timer_Control_Periodic_Msk = 1<<1
+Timer_Control_Trig_IRQ_Msk = 1<<2
+Timer_Control_Trig_DMA_Msk = 1<<3
+
+# DMA registers
+DMA_Control = 0
+
+# DMA Control register definitions
+DMA_Control_Enable_Msk    = 1<<0
+DMA_Control_Direction_Msk = 1<<1
+DMA_Control_Direction_P2M = 0<<1
+DMA_Control_Direction_M2P = 1<<1
+
+# User registers
+Regs = [0] * 64
+
+# Data buffer
+Data = bytearray()
+
+
+## Initialize
+#  @return None
+def init():
+    logging.info("Python function init() called")
+    vsi_video.init(server_address, server_authkey)
+
+
+## Read interrupt request (the VSI IRQ Status Register)
+#  @return value value read (32-bit)
+def rdIRQ():
+    global IRQ_Status
+    logging.info("Python function rdIRQ() called")
+
+    value = IRQ_Status
+    logging.debug("Read interrupt request: {}".format(value))
+
+    return value
+
+
+## Write interrupt request (the VSI IRQ Status Register)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrIRQ(value):
+    global IRQ_Status
+    logging.info("Python function wrIRQ() called")
+
+    value = vsi_video.wrIRQ(IRQ_Status, value)
+    IRQ_Status = value
+    logging.debug("Write interrupt request: {}".format(value))
+
+    return value
+
+
+## Write Timer registers (the VSI Timer Registers)
+#  @param index Timer register index (zero based)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrTimer(index, value):
+    global Timer_Control, Timer_Interval
+    logging.info("Python function wrTimer() called")
+
+    if   index == 0:
+        Timer_Control = value
+        logging.debug("Write Timer_Control: {}".format(value))
+    elif index == 1:
+        Timer_Interval = value
+        logging.debug("Write Timer_Interval: {}".format(value))
+
+    return value
+
+
+## Timer event (called at Timer Overflow)
+#  @return None
+def timerEvent():
+    global IRQ_Status
+
+    logging.info("Python function timerEvent() called")
+
+    IRQ_Status = vsi_video.timerEvent(IRQ_Status)
+
+
+## Write DMA registers (the VSI DMA Registers)
+#  @param index DMA register index (zero based)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrDMA(index, value):
+    global DMA_Control
+    logging.info("Python function wrDMA() called")
+
+    if   index == 0:
+        DMA_Control = value
+        logging.debug("Write DMA_Control: {}".format(value))
+
+    return value
+
+
+## Read data from peripheral for DMA P2M transfer (VSI DMA)
+#  @param size size of data to read (in bytes, multiple of 4)
+#  @return data data read (bytearray)
+def rdDataDMA(size):
+    global Data
+    logging.info("Python function rdDataDMA() called")
+
+    Data = vsi_video.rdDataDMA(size)
+
+    n = min(len(Data), size)
+    data = bytearray(size)
+    data[0:n] = Data[0:n]
+    logging.debug("Read data ({} bytes)".format(size))
+
+    return data
+
+
+## Write data to peripheral for DMA M2P transfer (VSI DMA)
+#  @param data data to write (bytearray)
+#  @param size size of data to write (in bytes, multiple of 4)
+#  @return None
+def wrDataDMA(data, size):
+    global Data
+    logging.info("Python function wrDataDMA() called")
+
+    Data = data
+    logging.debug("Write data ({} bytes)".format(size))
+
+    vsi_video.wrDataDMA(data, size)
+
+    return
+
+
+## Read user registers (the VSI User Registers)
+#  @param index user register index (zero based)
+#  @return value value read (32-bit)
+def rdRegs(index):
+    global Regs
+    logging.info("Python function rdRegs() called")
+
+    if index <= vsi_video.REG_IDX_MAX:
+        Regs[index] = vsi_video.rdRegs(index)
+
+    value = Regs[index]
+    logging.debug("Read user register at index {}: {}".format(index, value))
+
+    return value
+
+
+## Write user registers (the VSI User Registers)
+#  @param index user register index (zero based)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrRegs(index, value):
+    global Regs
+    logging.info("Python function wrRegs() called")
+
+    if index <= vsi_video.REG_IDX_MAX:
+        value = vsi_video.wrRegs(index, value)
+
+    Regs[index] = value
+    logging.debug("Write user register at index {}: {}".format(index, value))
+
+    return value
+
+
+## @}
+
diff --git a/scripts/py/vsi/arm_vsi7.py b/scripts/py/vsi/arm_vsi7.py
new file mode 100644
index 0000000..892433c
--- /dev/null
+++ b/scripts/py/vsi/arm_vsi7.py
@@ -0,0 +1,207 @@
+#!/usr/bin/env python3
+#  SPDX-FileCopyrightText:  Copyright 2024 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
+#
+#      http://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 logging
+import vsi_video
+
+## Set verbosity level
+#verbosity = logging.DEBUG
+verbosity = logging.ERROR
+
+# [debugging] Verbosity settings
+level = { 10: "DEBUG",  20: "INFO",  30: "WARNING",  40: "ERROR" }
+logging.basicConfig(format='Py: VSI7: [%(levelname)s]\t%(message)s', level = verbosity)
+logging.info("Verbosity level is set to " + level[verbosity])
+
+
+# Video Server configuration
+server_address = ('127.0.0.1', 6003)
+server_authkey = 'vsi_video'
+
+
+# IRQ registers
+IRQ_Status = 0
+
+# Timer registers
+Timer_Control  = 0
+Timer_Interval = 0
+
+# Timer Control register definitions
+Timer_Control_Run_Msk      = 1<<0
+Timer_Control_Periodic_Msk = 1<<1
+Timer_Control_Trig_IRQ_Msk = 1<<2
+Timer_Control_Trig_DMA_Msk = 1<<3
+
+# DMA registers
+DMA_Control = 0
+
+# DMA Control register definitions
+DMA_Control_Enable_Msk    = 1<<0
+DMA_Control_Direction_Msk = 1<<1
+DMA_Control_Direction_P2M = 0<<1
+DMA_Control_Direction_M2P = 1<<1
+
+# User registers
+Regs = [0] * 64
+
+# Data buffer
+Data = bytearray()
+
+
+## Initialize
+#  @return None
+def init():
+    logging.info("Python function init() called")
+    vsi_video.init(server_address, server_authkey)
+
+
+## Read interrupt request (the VSI IRQ Status Register)
+#  @return value value read (32-bit)
+def rdIRQ():
+    global IRQ_Status
+    logging.info("Python function rdIRQ() called")
+
+    value = IRQ_Status
+    logging.debug("Read interrupt request: {}".format(value))
+
+    return value
+
+
+## Write interrupt request (the VSI IRQ Status Register)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrIRQ(value):
+    global IRQ_Status
+    logging.info("Python function wrIRQ() called")
+
+    value = vsi_video.wrIRQ(IRQ_Status, value)
+    IRQ_Status = value
+    logging.debug("Write interrupt request: {}".format(value))
+
+    return value
+
+
+## Write Timer registers (the VSI Timer Registers)
+#  @param index Timer register index (zero based)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrTimer(index, value):
+    global Timer_Control, Timer_Interval
+    logging.info("Python function wrTimer() called")
+
+    if   index == 0:
+        Timer_Control = value
+        logging.debug("Write Timer_Control: {}".format(value))
+    elif index == 1:
+        Timer_Interval = value
+        logging.debug("Write Timer_Interval: {}".format(value))
+
+    return value
+
+
+## Timer event (called at Timer Overflow)
+#  @return None
+def timerEvent():
+    global IRQ_Status
+
+    logging.info("Python function timerEvent() called")
+
+    IRQ_Status = vsi_video.timerEvent(IRQ_Status)
+
+
+## Write DMA registers (the VSI DMA Registers)
+#  @param index DMA register index (zero based)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrDMA(index, value):
+    global DMA_Control
+    logging.info("Python function wrDMA() called")
+
+    if   index == 0:
+        DMA_Control = value
+        logging.debug("Write DMA_Control: {}".format(value))
+
+    return value
+
+
+## Read data from peripheral for DMA P2M transfer (VSI DMA)
+#  @param size size of data to read (in bytes, multiple of 4)
+#  @return data data read (bytearray)
+def rdDataDMA(size):
+    global Data
+    logging.info("Python function rdDataDMA() called")
+
+    Data = vsi_video.rdDataDMA(size)
+
+    n = min(len(Data), size)
+    data = bytearray(size)
+    data[0:n] = Data[0:n]
+    logging.debug("Read data ({} bytes)".format(size))
+
+    return data
+
+
+## Write data to peripheral for DMA M2P transfer (VSI DMA)
+#  @param data data to write (bytearray)
+#  @param size size of data to write (in bytes, multiple of 4)
+#  @return None
+def wrDataDMA(data, size):
+    global Data
+    logging.info("Python function wrDataDMA() called")
+
+    Data = data
+    logging.debug("Write data ({} bytes)".format(size))
+
+    vsi_video.wrDataDMA(data, size)
+
+    return
+
+
+## Read user registers (the VSI User Registers)
+#  @param index user register index (zero based)
+#  @return value value read (32-bit)
+def rdRegs(index):
+    global Regs
+    logging.info("Python function rdRegs() called")
+
+    if index <= vsi_video.REG_IDX_MAX:
+        Regs[index] = vsi_video.rdRegs(index)
+
+    value = Regs[index]
+    logging.debug("Read user register at index {}: {}".format(index, value))
+
+    return value
+
+
+## Write user registers (the VSI User Registers)
+#  @param index user register index (zero based)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrRegs(index, value):
+    global Regs
+    logging.info("Python function wrRegs() called")
+
+    if index <= vsi_video.REG_IDX_MAX:
+        value = vsi_video.wrRegs(index, value)
+
+    Regs[index] = value
+    logging.debug("Write user register at index {}: {}".format(index, value))
+
+    return value
+
+
+## @}
+
diff --git a/scripts/py/vsi/vsi_video.py b/scripts/py/vsi/vsi_video.py
new file mode 100644
index 0000000..88f44fb
--- /dev/null
+++ b/scripts/py/vsi/vsi_video.py
@@ -0,0 +1,461 @@
+#!/usr/bin/env python3
+#  SPDX-FileCopyrightText:  Copyright 2024 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
+#
+#      http://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 time
+import atexit
+import logging
+import subprocess
+from multiprocessing.connection import Client, Connection
+from os import path, getcwd
+from os import name as os_name
+
+
+class VideoClient:
+    def __init__(self):
+        # Server commands
+        self.SET_FILENAME     = 1
+        self.STREAM_CONFIGURE = 2
+        self.STREAM_ENABLE    = 3
+        self.STREAM_DISABLE   = 4
+        self.FRAME_READ       = 5
+        self.FRAME_WRITE      = 6
+        self.CLOSE_SERVER     = 7
+        # Color space
+        self.GRAYSCALE8       = 1
+        self.RGB888           = 2
+        self.BGR565           = 3
+        self.YUV420           = 4
+        self.NV12             = 5
+        self.NV21             = 6
+        # Variables
+        self.conn = None
+
+    def connectToServer(self, address, authkey):
+        for _ in range(50):
+            try:
+                self.conn = Client(address, authkey=authkey.encode('utf-8'))
+                if isinstance(self.conn, Connection):
+                    break
+                else:
+                    self.conn = None
+            except Exception:
+                self.conn = None
+            time.sleep(0.01)
+
+    def setFilename(self, filename, mode):
+        self.conn.send([self.SET_FILENAME, getcwd(), filename, mode])
+        filename_valid = self.conn.recv()
+
+        return filename_valid
+
+    def configureStream(self, frame_width, frame_height, color_format, frame_rate):
+        self.conn.send([self.STREAM_CONFIGURE, frame_width, frame_height, color_format, frame_rate])
+        configuration_valid = self.conn.recv()
+
+        return configuration_valid
+
+    def enableStream(self, mode):
+        self.conn.send([self.STREAM_ENABLE, mode])
+        stream_active = self.conn.recv()
+
+        return stream_active
+
+    def disableStream(self):
+        self.conn.send([self.STREAM_DISABLE])
+        stream_active = self.conn.recv()
+
+        return stream_active
+
+    def readFrame(self):
+        self.conn.send([self.FRAME_READ])
+        data = self.conn.recv_bytes()
+        eos  = self.conn.recv()
+
+        return data, eos
+
+    def writeFrame(self, data):
+        self.conn.send([self.FRAME_WRITE])
+        self.conn.send_bytes(data)
+
+    def closeServer(self):
+        try:
+            if isinstance(self.conn, Connection):
+                self.conn.send([self.CLOSE_SERVER])
+                self.conn.close()
+        except Exception as e:
+            logging.error(f'Exception occurred on cleanup: {e}')
+
+
+# User registers
+REG_IDX_MAX               = 12  # Maximum user register index used in VSI
+MODE                      = 0   # Regs[0]  // Mode: 0=Input, 1=Output
+CONTROL                   = 0   # Regs[1]  // Control: enable, flush
+STATUS                    = 0   # Regs[2]  // Status: active, buf_empty, buf_full, overflow, underflow, eos
+FILENAME_LEN              = 0   # Regs[3]  // Filename length
+FILENAME_CHAR             = 0   # Regs[4]  // Filename character
+FILENAME_VALID            = 0   # Regs[5]  // Filename valid flag
+FRAME_WIDTH               = 300 # Regs[6]  // Requested frame width
+FRAME_HEIGHT              = 300 # Regs[7]  // Requested frame height
+COLOR_FORMAT              = 0   # Regs[8]  // Color format
+FRAME_RATE                = 0   # Regs[9]  // Frame rate
+FRAME_INDEX               = 0   # Regs[10] // Frame index
+FRAME_COUNT               = 0   # Regs[11] // Frame count
+FRAME_COUNT_MAX           = 0   # Regs[12] // Frame count maximum
+
+# MODE register definitions
+MODE_IO_Msk               = 1<<0
+MODE_Input                = 0<<0
+MODE_Output               = 1<<0
+
+# CONTROL register definitions
+CONTROL_ENABLE_Msk        = 1<<0
+CONTROL_CONTINUOS_Msk     = 1<<1
+CONTROL_BUF_FLUSH_Msk     = 1<<2
+
+# STATUS register definitions
+STATUS_ACTIVE_Msk         = 1<<0
+STATUS_BUF_EMPTY_Msk      = 1<<1
+STATUS_BUF_FULL_Msk       = 1<<2
+STATUS_OVERFLOW_Msk       = 1<<3
+STATUS_UNDERFLOW_Msk      = 1<<4
+STATUS_EOS_Msk            = 1<<5
+
+# IRQ Status register definitions
+IRQ_Status_FRAME_Msk      = 1<<0
+IRQ_Status_OVERFLOW_Msk   = 1<<1
+IRQ_Status_UNDERFLOW_Msk  = 1<<2
+IRQ_Status_EOS_Msk        = 1<<3
+
+# Variables
+Video                     = VideoClient()
+Filename                  = ""
+FilenameIdx               = 0
+
+
+# Close VSI Video Server on exit
+def cleanup():
+    Video.closeServer()
+
+
+# Client connection to VSI Video Server
+def init(address, authkey):
+    global FILENAME_VALID
+
+    base_dir = path.dirname(__file__)
+    server_path = path.join(base_dir, 'vsi_video_server.py')
+
+    logging.info("Start video server")
+    if path.isfile(server_path):
+        # Start Video Server
+        if os_name == 'nt':
+            py_cmd = 'python'
+        else:
+            py_cmd = 'python3'
+        cmd = f"{py_cmd} {server_path} " \
+              f"--ip {address[0]} " \
+              f"--port {address[1]} " \
+              f"--authkey {authkey}"
+        subprocess.Popen(cmd, shell=True)
+        # Connect to Video Server
+        Video.connectToServer(address, authkey)
+        if Video.conn == None:
+            logging.error("Server not connected")
+
+    else:
+        logging.error(f"Server script not found: {server_path}")
+
+    # Register clean-up function
+    atexit.register(cleanup)
+
+
+## Flush Stream buffer
+def flushBuffer():
+    global STATUS, FRAME_INDEX, FRAME_COUNT
+
+    STATUS |=  STATUS_BUF_EMPTY_Msk
+    STATUS &= ~STATUS_BUF_FULL_Msk
+
+    FRAME_INDEX = 0
+    FRAME_COUNT = 0
+
+
+## VSI IRQ Status register
+#  @param IRQ_Status IRQ status register to update
+#  @param value status bits to clear
+#  @return IRQ_Status return updated register
+def wrIRQ(IRQ_Status, value):
+    IRQ_Status_Clear = IRQ_Status & ~value
+    IRQ_Status &= ~IRQ_Status_Clear
+
+    return IRQ_Status
+
+
+## Timer Event
+#  @param IRQ_Status IRQ status register to update
+#  @return IRQ_Status return updated register
+def timerEvent(IRQ_Status):
+
+    IRQ_Status |= IRQ_Status_FRAME_Msk
+
+    if (STATUS & STATUS_OVERFLOW_Msk) != 0:
+        IRQ_Status |= IRQ_Status_OVERFLOW_Msk
+
+    if (STATUS & STATUS_UNDERFLOW_Msk) != 0:
+        IRQ_Status |= IRQ_Status_UNDERFLOW_Msk
+
+    if (STATUS & STATUS_EOS_Msk) != 0:
+        IRQ_Status |= IRQ_Status_EOS_Msk
+
+    if (CONTROL & CONTROL_CONTINUOS_Msk) == 0:
+        wrCONTROL(CONTROL & ~(CONTROL_ENABLE_Msk | CONTROL_CONTINUOS_Msk))
+
+    return IRQ_Status
+
+
+## Read data from peripheral for DMA P2M transfer (VSI DMA)
+#  @param size size of data to read (in bytes, multiple of 4)
+#  @return data data read (bytearray)
+def rdDataDMA(size):
+    global STATUS, FRAME_COUNT
+
+    if (STATUS & STATUS_ACTIVE_Msk) != 0:
+
+        if Video.conn != None:
+            data, eos = Video.readFrame()
+            if eos:
+                STATUS |= STATUS_EOS_Msk
+            if FRAME_COUNT < FRAME_COUNT_MAX:
+                FRAME_COUNT += 1
+            else:
+                STATUS |= STATUS_OVERFLOW_Msk
+            if FRAME_COUNT == FRAME_COUNT_MAX:
+                STATUS |= STATUS_BUF_FULL_Msk
+            STATUS &= ~STATUS_BUF_EMPTY_Msk
+        else:
+            data = bytearray()
+
+    else:
+        data = bytearray()
+
+    return data
+
+
+## Write data to peripheral for DMA M2P transfer (VSI DMA)
+#  @param data data to write (bytearray)
+#  @param size size of data to write (in bytes, multiple of 4)
+def wrDataDMA(data, size):
+    global STATUS, FRAME_COUNT
+
+    if (STATUS & STATUS_ACTIVE_Msk) != 0:
+
+        if Video.conn != None:
+            Video.writeFrame(data)
+            if FRAME_COUNT > 0:
+                FRAME_COUNT -= 1
+            else:
+                STATUS |= STATUS_UNDERFLOW_Msk
+            if FRAME_COUNT == 0:
+                STATUS |= STATUS_BUF_EMPTY_Msk
+            STATUS &= ~STATUS_BUF_FULL_Msk
+
+
+## Write CONTROL register (user register)
+#  @param value value to write (32-bit)
+def wrCONTROL(value):
+    global CONTROL, STATUS
+
+    if ((value ^ CONTROL) & CONTROL_ENABLE_Msk) != 0:
+        STATUS &= ~STATUS_ACTIVE_Msk
+        if (value & CONTROL_ENABLE_Msk) != 0:
+            logging.info("Start video stream")
+            if Video.conn != None:
+                logging.info("Configure video stream")
+                configuration_valid = Video.configureStream(FRAME_WIDTH, FRAME_HEIGHT, COLOR_FORMAT, FRAME_RATE)
+                if configuration_valid:
+                    logging.info("Enable video stream")
+                    server_active = Video.enableStream(MODE)
+                    if server_active:
+                        STATUS |=   STATUS_ACTIVE_Msk
+                        STATUS &= ~(STATUS_OVERFLOW_Msk | STATUS_UNDERFLOW_Msk | STATUS_EOS_Msk)
+                    else:
+                        logging.error("Enable video stream failed")
+                else:
+                    logging.error("Configure video stream failed")
+            else:
+                logging.error("Server not connected")
+        else:
+            logging.info("Stop video stream")
+            if Video.conn != None:
+                logging.info("Disable video stream")
+                Video.disableStream()
+            else:
+                logging.error("Server not connected")
+
+    if (value & CONTROL_BUF_FLUSH_Msk) != 0:
+        value &= ~CONTROL_BUF_FLUSH_Msk
+        flushBuffer()
+
+    CONTROL = value
+
+
+## Read STATUS register (user register)
+# @return status current STATUS User register (32-bit)
+def rdSTATUS():
+    global STATUS
+
+    status = STATUS
+    STATUS &= ~(STATUS_OVERFLOW_Msk | STATUS_UNDERFLOW_Msk | STATUS_EOS_Msk)
+
+    return status
+
+
+## Write FILENAME_LEN register (user register)
+#  @param value value to write (32-bit)
+def wrFILENAME_LEN(value):
+    global STATUS, FILENAME_LEN, FILENAME_VALID, Filename, FilenameIdx
+
+    logging.info("Set new source name length and reset filename and valid flag")
+    FilenameIdx = 0
+    Filename = ""
+    FILENAME_VALID = 0
+    FILENAME_LEN = value
+
+
+## Write FILENAME_CHAR register (user register)
+#  @param value value to write (32-bit)
+def wrFILENAME_CHAR(value):
+    global FILENAME_VALID, Filename, FilenameIdx
+
+    if FilenameIdx < FILENAME_LEN:
+        logging.info(f"Append {value} to filename")
+        Filename += f"{value}"
+        FilenameIdx += 1
+        logging.debug(f"Received {FilenameIdx} of {FILENAME_LEN} characters")
+
+    if FilenameIdx == FILENAME_LEN:
+        logging.info("Check if file exists on Server side and set VALID flag")
+        logging.debug(f"Filename: {Filename}")
+
+        if Video.conn != None:
+            FILENAME_VALID = Video.setFilename(Filename, MODE)
+        else:
+            logging.error("Server not connected")
+
+        logging.debug(f"Filename VALID: {FILENAME_VALID}")
+
+
+## Write FRAME_INDEX register (user register)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrFRAME_INDEX(value):
+    global STATUS, FRAME_INDEX, FRAME_COUNT
+
+    FRAME_INDEX += 1
+    if FRAME_INDEX == FRAME_COUNT_MAX:
+        FRAME_INDEX = 0
+
+    if (MODE & MODE_IO_Msk) == MODE_Input:
+        # Input
+        if FRAME_COUNT > 0:
+            FRAME_COUNT -= 1
+        if FRAME_COUNT == 0:
+            STATUS |= STATUS_BUF_EMPTY_Msk
+        STATUS &= ~STATUS_BUF_FULL_Msk
+    else:
+        # Output
+        if FRAME_COUNT < FRAME_COUNT_MAX:
+            FRAME_COUNT += 1
+        if FRAME_COUNT == FRAME_COUNT_MAX:
+            STATUS |= STATUS_BUF_FULL_Msk
+        STATUS &= ~STATUS_BUF_EMPTY_Msk
+
+    return FRAME_INDEX
+
+
+## Read user registers (the VSI User Registers)
+#  @param index user register index (zero based)
+#  @return value value read (32-bit)
+def rdRegs(index):
+    value = 0
+
+    if   index == 0:
+        value = MODE
+    elif index == 1:
+        value = CONTROL
+    elif index == 2:
+        value = rdSTATUS()
+    elif index == 3:
+        value = FILENAME_LEN
+    elif index == 4:
+        value = FILENAME_CHAR
+    elif index == 5:
+        value = FILENAME_VALID
+    elif index == 6:
+        value = FRAME_WIDTH
+    elif index == 7:
+        value = FRAME_HEIGHT
+    elif index == 8:
+        value = COLOR_FORMAT
+    elif index == 9:
+        value = FRAME_RATE
+    elif index == 10:
+        value = FRAME_INDEX
+    elif index == 11:
+        value = FRAME_COUNT
+    elif index == 12:
+        value = FRAME_COUNT_MAX
+
+    return value
+
+
+## Write user registers (the VSI User Registers)
+#  @param index user register index (zero based)
+#  @param value value to write (32-bit)
+#  @return value value written (32-bit)
+def wrRegs(index, value):
+    global MODE, FRAME_WIDTH, FRAME_HEIGHT, COLOR_FORMAT, FRAME_RATE, FRAME_COUNT_MAX
+
+    if   index == 0:
+        MODE = value
+    elif index == 1:
+        wrCONTROL(value)
+    elif index == 2:
+        value = STATUS
+    elif index == 3:
+        wrFILENAME_LEN(value)
+    elif index == 4:
+        wrFILENAME_CHAR(chr(value))
+    elif index == 5:
+        value = FILENAME_VALID
+    elif index == 6:
+        if value != 0:
+            FRAME_WIDTH = value
+    elif index == 7:
+        if value != 0:
+            FRAME_HEIGHT = value
+    elif index == 8:
+        COLOR_FORMAT = value
+    elif index == 9:
+        FRAME_RATE = value
+    elif index == 10:
+        value = wrFRAME_INDEX(value)
+    elif index == 11:
+        value = FRAME_COUNT
+    elif index == 12:
+        FRAME_COUNT_MAX = value
+        flushBuffer()
+
+    return value
diff --git a/scripts/py/vsi/vsi_video_server.py b/scripts/py/vsi/vsi_video_server.py
new file mode 100644
index 0000000..f98b2ac
--- /dev/null
+++ b/scripts/py/vsi/vsi_video_server.py
@@ -0,0 +1,447 @@
+#!/usr/bin/env python3
+#  SPDX-FileCopyrightText:  Copyright 2024 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
+#
+#      http://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 ipaddress
+import logging
+import os
+from multiprocessing.connection import Listener
+
+import cv2
+import numpy as np
+
+## Set verbosity level
+verbosity = logging.ERROR
+
+# [debugging] Verbosity settings
+level = { 10: "DEBUG",  20: "INFO",  30: "WARNING",  40: "ERROR" }
+logging.basicConfig(format='VSI Server: [%(levelname)s]\t%(message)s', level = verbosity)
+logging.info("Verbosity level is set to " + level[verbosity])
+
+# Default Server configuration
+default_address       = ('127.0.0.1', 6000)
+default_authkey       = 'vsi_video'
+
+# Supported file extensions
+video_file_extensions = ('wmv', 'avi', 'mp4')
+image_file_extensions = ('bmp', 'png', 'jpg')
+video_fourcc          = {'wmv' : 'WMV1', 'avi' : 'MJPG', 'mp4' : 'mp4v'}
+
+# Mode Input/Output
+MODE_IO_Msk           = 1<<0
+MODE_Input            = 0<<0
+MODE_Output           = 1<<0
+
+class VideoServer:
+    def __init__(self, address, authkey):
+        # Server commands
+        self.SET_FILENAME     = 1
+        self.STREAM_CONFIGURE = 2
+        self.STREAM_ENABLE    = 3
+        self.STREAM_DISABLE   = 4
+        self.FRAME_READ       = 5
+        self.FRAME_WRITE      = 6
+        self.CLOSE_SERVER     = 7
+        # Color space
+        self.GRAYSCALE8       = 1
+        self.RGB888           = 2
+        self.BGR565           = 3
+        self.YUV420           = 4
+        self.NV12             = 5
+        self.NV21             = 6
+        # Variables
+        self.listener         = Listener(address, authkey=authkey.encode('utf-8'))
+        self.filename         = ""
+        self.mode             = None
+        self.active           = False
+        self.video            = True
+        self.stream           = None
+        self.frame_ratio      = 0
+        self.frame_drop       = 0
+        self.frame_index      = 0
+        self.eos              = False
+        # Stream configuration
+        self.resolution       = (None, None)
+        self.color_format     = None
+        self.frame_rate       = None
+
+    # Set filename
+    def _setFilename(self, base_dir, filename, mode):
+        filename_valid = False
+
+        if self.active:
+            return filename_valid
+
+        self.filename    = ""
+        self.frame_index = 0
+
+        file_extension = str(filename).split('.')[-1].lower()
+
+        if file_extension in video_file_extensions:
+            self.video = True
+        else:
+            self.video = False
+
+        file_path = os.path.join(base_dir, filename)
+        logging.debug(f"File path: {file_path}")
+
+        if (mode & MODE_IO_Msk) == MODE_Input:
+            self.mode = MODE_Input
+            if os.path.isfile(file_path):
+                if file_extension in (video_file_extensions + image_file_extensions):
+                    self.filename  = file_path
+                    filename_valid = True
+        else:
+            self.mode = MODE_Output
+            if file_extension in (video_file_extensions + image_file_extensions):
+                if os.path.isfile(file_path):
+                    os.remove(file_path)
+                self.filename  = file_path
+                filename_valid = True
+
+        return filename_valid
+
+    # Configure video stream
+    def _configureStream(self, frame_width, frame_height, color_format, frame_rate):
+        if (frame_width == 0 or frame_height == 0 or frame_rate == 0):
+            return False
+
+        self.resolution   = (frame_width, frame_height)
+        self.color_format = color_format
+        self.frame_rate   = frame_rate
+
+        return True
+
+    # Enable video stream
+    def _enableStream(self, mode):
+        if self.active:
+            return
+
+        self.eos = False
+        self.frame_ratio = 0
+        self.frame_drop  = 0
+
+        if self.stream is not None:
+            self.stream.release()
+            self.stream = None
+
+        if self.filename == "":
+            self.video = True
+            if (mode & MODE_IO_Msk) == MODE_Input:
+                # Device mode: camera
+                self.mode = MODE_Input
+            else:
+                # Device mode: display
+                self.mode = MODE_Output
+
+        if self.video:
+            if self.mode == MODE_Input:
+                if self.filename == "":
+                    self.stream = cv2.VideoCapture(0)
+                    if not self.stream.isOpened():
+                        logging.error("Failed to open Camera interface")
+                        return
+                else:
+                    self.stream = cv2.VideoCapture(self.filename)
+                    self.stream.set(cv2.CAP_PROP_POS_FRAMES, self.frame_index)
+                    video_fps = self.stream.get(cv2.CAP_PROP_FPS)
+                    if video_fps > self.frame_rate:
+                        self.frame_ratio = video_fps / self.frame_rate
+                        logging.debug(f"Frame ratio: {self.frame_ratio}")
+            else:
+                if self.filename != "":
+                    extension = str(self.filename).split('.')[-1].lower()
+                    fourcc = cv2.VideoWriter_fourcc(*f'{video_fourcc[extension]}')
+
+                    if os.path.isfile(self.filename) and (self.frame_index != 0):
+                        tmp_filename = f'{self.filename.rstrip(f".{extension}")}_tmp.{extension}'
+                        os.rename(self.filename, tmp_filename)
+                        cap    = cv2.VideoCapture(tmp_filename)
+                        width  = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
+                        height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
+                        self.resolution = (width, height)
+                        self.frame_rate = cap.get(cv2.CAP_PROP_FPS)
+                        self.stream = cv2.VideoWriter(self.filename, fourcc, self.frame_rate, self.resolution)
+
+                        while cap.isOpened():
+                            ret, frame = cap.read()
+                            if not ret:
+                                cap.release()
+                                os.remove(tmp_filename)
+                                break
+                            self.stream.write(frame)
+                            del frame
+
+                    else:
+                        self.stream = cv2.VideoWriter(self.filename, fourcc, self.frame_rate, self.resolution)
+
+        self.active = True
+        logging.info("Stream enabled")
+
+    # Disable Video Server
+    def _disableStream(self):
+        self.active = False
+        if self.stream is not None:
+            if self.mode == MODE_Input:
+                self.frame_index = self.stream.get(cv2.CAP_PROP_POS_FRAMES)
+            self.stream.release()
+            self.stream = None
+        logging.info("Stream disabled")
+
+    # Resize frame to requested resolution in pixels
+    def __resizeFrame(self, frame, resolution):
+        frame_h = frame.shape[0]
+        frame_w = frame.shape[1]
+
+        # Calculate requested aspect ratio (width/height):
+        crop_aspect_ratio  = resolution[0] / resolution[1]
+
+        if crop_aspect_ratio != (frame_w / frame_h):
+            # Crop into image with resize aspect ratio
+            crop_w = int(frame_h * crop_aspect_ratio)
+            crop_h = int(frame_w / crop_aspect_ratio)
+
+            if   crop_w > frame_w:
+                # Crop top and bottom part of the image
+                top    = (frame_h - crop_h) // 2
+                bottom = top + crop_h
+                frame  = frame[top : bottom, 0 : frame_w]
+            elif crop_h > frame_h:
+                # Crop left and right side of the image``
+                left   = (frame_w - crop_w) // 2
+                right  = left + crop_w
+                frame  = frame[0 : frame_h, left : right]
+            else:
+                # Crop to the center of the image
+                left   = (frame_w - crop_w) // 2
+                right  = left + crop_w
+                top    = (frame_h - crop_h) // 2
+                bottom = top + crop_h
+                frame  = frame[top : bottom, left : right]
+            logging.debug(f"Frame cropped from ({frame_w}, {frame_h}) to ({frame.shape[1]}, {frame.shape[0]})")
+
+        logging.debug(f"Resize frame from ({frame.shape[1]}, {frame.shape[0]}) to ({resolution[0]}, {resolution[1]})")
+        try:
+            frame = cv2.resize(frame, resolution)
+        except Exception as e:
+            logging.error(f"Error in resizeFrame(): {e}")
+
+        return frame
+
+    # Change color space of a frame from BGR to selected profile
+    def __changeColorSpace(self, frame, color_space):
+        color_format = None
+
+        # Default OpenCV color profile: BGR
+        if self.mode == MODE_Input:
+            if   color_space == self.GRAYSCALE8:
+                color_format = cv2.COLOR_BGR2GRAY
+            elif color_space == self.RGB888:
+                color_format = cv2.COLOR_BGR2RGB
+            elif color_space == self.BGR565:
+                color_format = cv2.COLOR_BGR2BGR565
+            elif color_space == self.YUV420:
+                color_format = cv2.COLOR_BGR2YUV_I420
+            elif color_space == self.NV12:
+                frame = self.__changeColorSpace(frame, self.YUV420)
+                color_format = cv2.COLOR_YUV2RGB_NV12
+            elif color_space == self.NV21:
+                frame = self.__changeColorSpace(frame, self.YUV420)
+                color_format = cv2.COLOR_YUV2RGB_NV21
+
+        else:
+            if   color_space == self.GRAYSCALE8:
+                color_format = cv2.COLOR_GRAY2BGR
+            elif color_space == self.RGB888:
+                color_format = cv2.COLOR_RGB2BGR
+            elif color_space == self.BGR565:
+                color_format = cv2.COLOR_BGR5652BGR
+            elif color_space == self.YUV420:
+                color_format = cv2.COLOR_YUV2BGR_I420
+            elif color_space == self.NV12:
+                color_format = cv2.COLOR_YUV2BGR_I420
+            elif color_space == self.NV21:
+                color_format = cv2.COLOR_YUV2BGR_I420
+
+        if color_format != None:
+            logging.debug(f"Change color space to {color_format}")
+            try:
+                frame = cv2.cvtColor(frame, color_format)
+            except Exception as e:
+                logging.error(f"Error in changeColorSpace(): {e}")
+
+        return frame
+
+    # Read frame from source
+    def _readFrame(self):
+        frame = bytearray()
+
+        if not self.active:
+            return frame
+
+        if self.eos:
+            return frame
+
+        if self.video:
+            if self.frame_ratio > 1:
+                _, tmp_frame = self.stream.read()
+                self.frame_drop += (self.frame_ratio - 1)
+                if self.frame_drop > 1:
+                    logging.debug(f"Frames to drop: {self.frame_drop}")
+                    drop = int(self.frame_drop // 1)
+                    for i in range(drop):
+                        _, _ = self.stream.read()
+                    logging.debug(f"Frames dropped: {drop}")
+                    self.frame_drop -= drop
+                    logging.debug(f"Frames left to drop: {self.frame_drop}")
+            else:
+                _, tmp_frame = self.stream.read()
+            if tmp_frame is None:
+                self.eos = True
+                logging.debug("End of stream.")
+        else:
+            tmp_frame = cv2.imread(self.filename)
+            self.eos  = True
+            logging.debug("End of stream.")
+
+        if tmp_frame is not None:
+            tmp_frame = self.__resizeFrame(tmp_frame, self.resolution)
+            tmp_frame = self.__changeColorSpace(tmp_frame, self.color_format)
+            frame = bytearray(tmp_frame.tobytes())
+
+        return frame
+
+    # Write frame to destination
+    def _writeFrame(self, frame):
+        if not self.active:
+            return
+
+        try:
+            decoded_frame = np.frombuffer(frame, dtype=np.uint8)
+            decoded_frame = decoded_frame.reshape((self.resolution[0], self.resolution[1], 3))
+            bgr_frame = self.__changeColorSpace(decoded_frame, self.RGB888)
+
+            if self.filename == "":
+                cv2.imshow(self.filename, bgr_frame)
+                cv2.waitKey(10)
+            else:
+                if self.video:
+                    self.stream.write(np.uint8(bgr_frame))
+                    self.frame_index += 1
+                else:
+                    cv2.imwrite(self.filename, bgr_frame)
+        except Exception:
+            pass
+
+    # Run Video Server
+    def run(self):
+        logging.info("Video server started")
+
+        try:
+            conn = self.listener.accept()
+            logging.info(f'Connection accepted {self.listener.address}')
+        except Exception:
+            logging.error("Connection not accepted")
+            return
+
+        while True:
+            try:
+                recv = conn.recv()
+            except EOFError:
+                return
+
+            cmd     = recv[0]  # Command
+            payload = recv[1:] # Payload
+
+            if  cmd == self.SET_FILENAME:
+                logging.info("Set filename called")
+                filename_valid = self._setFilename(payload[0], payload[1], payload[2])
+                conn.send(filename_valid)
+
+            elif cmd == self.STREAM_CONFIGURE:
+                logging.info("Stream configure called")
+                configuration_valid = self._configureStream(payload[0], payload[1], payload[2], payload[3])
+                conn.send(configuration_valid)
+
+            elif cmd == self.STREAM_ENABLE:
+                logging.info("Enable stream called")
+                self._enableStream(payload[0])
+                conn.send(self.active)
+
+            elif cmd == self.STREAM_DISABLE:
+                logging.info("Disable stream called")
+                self._disableStream()
+                conn.send(self.active)
+
+            elif cmd == self.FRAME_READ:
+                logging.info("Read frame called")
+                frame = self._readFrame()
+                conn.send_bytes(frame)
+                conn.send(self.eos)
+
+            elif cmd == self.FRAME_WRITE:
+                logging.info("Write frame called")
+                frame = conn.recv_bytes()
+                self._writeFrame(frame)
+
+            elif cmd == self.CLOSE_SERVER:
+                logging.info("Close server connection")
+                self.stop()
+
+    # Stop Video Server
+    def stop(self):
+        self._disableStream()
+        if (self.mode == MODE_Output) and (self.filename == ""):
+            try:
+                cv2.destroyAllWindows()
+            except Exception:
+                pass
+        self.listener.close()
+        logging.info("Video server stopped")
+
+
+# Validate IP address
+def ip(ip):
+    try:
+        _ = ipaddress.ip_address(ip)
+        return ip
+    except:
+        raise argparse.ArgumentTypeError(f"Invalid IP address: {ip}!")
+
+def parse_arguments():
+    formatter = lambda prog: argparse.HelpFormatter(prog, max_help_position=41)
+    parser = argparse.ArgumentParser(formatter_class=formatter, description="VSI Video Server")
+
+    parser_optional = parser.add_argument_group("optional")
+    parser_optional.add_argument("--ip", dest="ip",  metavar="<IP>",
+                                 help=f"Server IP address (default: {default_address[0]})",
+                                 type=ip, default=default_address[0])
+    parser_optional.add_argument("--port", dest="port",  metavar="<TCP Port>",
+                                 help=f"TCP port (default: {default_address[1]})",
+                                 type=int, default=default_address[1])
+    parser_optional.add_argument("--authkey", dest="authkey",  metavar="<Auth Key>",
+                                 help=f"Authorization key (default: {default_authkey})",
+                                 type=str, default=default_authkey)
+
+    return parser.parse_args()
+
+if __name__ == '__main__':
+    args = parse_arguments()
+    Server = VideoServer((args.ip, args.port), args.authkey)
+    try:
+        Server.run()
+    except KeyboardInterrupt:
+        Server.stop()
diff --git a/source/application/main/include/UseCaseCommonUtils.hpp b/source/application/main/include/UseCaseCommonUtils.hpp
index 68ffe42..182aea6 100644
--- a/source/application/main/include/UseCaseCommonUtils.hpp
+++ b/source/application/main/include/UseCaseCommonUtils.hpp
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: Copyright 2021-2022  Arm Limited and/or its affiliates <open-source-office@arm.com>
+ * SPDX-FileCopyrightText: Copyright 2021-2022, 2024 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");
@@ -56,12 +56,15 @@
 
 namespace common {
 
-  enum OPCODES {
-        MENU_OPT_RUN_INF_NEXT = 1, /* Run on next vector. */
-        MENU_OPT_RUN_INF_CHOSEN, /* Run on a user provided vector index. */
-        MENU_OPT_RUN_INF_ALL, /* Run inference on all. */
-        MENU_OPT_SHOW_MODEL_INFO, /* Show model info. */
-        MENU_OPT_LIST_IFM /* List the current IFM. */
+    enum OPCODES {
+        MENU_OPT_RUN_INF_NEXT = 1,   /* Run on next vector. */
+        MENU_OPT_RUN_INF_CHOSEN,     /* Run on a user provided vector index. */
+        MENU_OPT_RUN_INF_ALL,        /* Run inference on all. */
+        MENU_OPT_SHOW_MODEL_INFO,    /* Show model info. */
+        MENU_OPT_LIST_IFM,           /* List the current IFM. */
+#if VSI_ENABLED
+        MENU_OPT_RUN_INF_VSI         /* Run on input from VSI. */
+#endif
   };
 
 }
diff --git a/source/hal/source/components/vsi/CMakeLists.txt b/source/hal/source/components/vsi/CMakeLists.txt
new file mode 100644
index 0000000..8d07459
--- /dev/null
+++ b/source/hal/source/components/vsi/CMakeLists.txt
@@ -0,0 +1,79 @@
+#----------------------------------------------------------------------------
+#  SPDX-FileCopyrightText: Copyright 2024 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
+#
+#      http://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.
+#----------------------------------------------------------------------------
+
+##########################################################
+# Arm Virtual Streaming Interface initialization library #
+##########################################################
+
+# Arm Virtual Streaming Interface is only available on
+# certain supported platforms.
+
+cmake_minimum_required(VERSION 3.21.0)
+set(ARM_VSI_COMPONENT arm_vsi)
+project(${ARM_VSI_COMPONENT}
+    DESCRIPTION     "Arm Virtual Streaming Interface initialization library"
+    LANGUAGES       C CXX ASM)
+
+## Logging utilities:
+if (NOT TARGET log)
+    if (NOT DEFINED LOG_PROJECT_DIR)
+        message(FATAL_ERROR "LOG_PROJECT_DIR needs to be defined.")
+    endif()
+    add_subdirectory(${LOG_PROJECT_DIR} ${CMAKE_BINARY_DIR}/log)
+endif()
+
+# Create static library
+add_library(${ARM_VSI_COMPONENT} STATIC)
+
+## Include directories - public
+target_include_directories(${ARM_VSI_COMPONENT}
+    PUBLIC
+    include)
+
+## Component sources
+target_sources(${ARM_VSI_COMPONENT}
+    PUBLIC
+    source/video_drv.c)
+
+## If the rte_components target has been defined, include it as a dependency here. This component
+## gives access to certain CPU related functions and definitions that should come from the CMSIS
+## or custom system setup and boot implementation files.
+## If the component is not defined as a target, a dependency for this target should be added by
+## the project importing this one.
+if (TARGET rte_components)
+    target_link_libraries(${ARM_VSI_COMPONENT} PUBLIC
+            rte_components)
+else()
+    message(WARNING
+            "rte_components target not defined."
+            "${ARM_VSI_COMPONENT} will need to be provided access to"
+            "RTE_Components.h header to include CPU specific definitions.")
+endif()
+## Compile definitions
+target_compile_definitions(${ARM_VSI_COMPONENT}
+    PUBLIC
+    VSI_ENABLED)
+
+## Add dependencies
+target_link_libraries(${ARM_VSI_COMPONENT} PUBLIC
+    log)
+
+# Display status
+message(STATUS "CMAKE_CURRENT_SOURCE_DIR: " ${CMAKE_CURRENT_SOURCE_DIR})
+message(STATUS "*******************************************************")
+message(STATUS "Library                                : " ${ARM_VSI_COMPONENT})
+message(STATUS "*******************************************************")
diff --git a/source/hal/source/components/vsi/include/arm_vsi.h b/source/hal/source/components/vsi/include/arm_vsi.h
new file mode 100644
index 0000000..1d307d1
--- /dev/null
+++ b/source/hal/source/components/vsi/include/arm_vsi.h
@@ -0,0 +1,121 @@
+/*
+* SPDX-FileCopyrightText: Copyright 2024 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
+*
+*     http://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.
+*/
+
+/*
+ * Virtual Streaming Interface (VSI)
+ */
+
+#ifndef __ARM_VSI_H
+#define __ARM_VSI_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef __IM
+#define __IM  volatile const    /*! Defines 'read only' structure member permissions */
+#endif
+#ifndef __OM
+#define __OM  volatile          /*! Defines 'write only' structure member permissions */
+#endif
+#ifndef __IOM
+#define __IOM volatile          /*! Defines 'read/write' structure member permissions */
+#endif
+
+#include <stdint.h>
+
+/* IRQ number assignment (should be moved to device header) */
+#define ARM_VSI0_IRQn   224
+#define ARM_VSI1_IRQn   225
+#define ARM_VSI2_IRQn   226
+#define ARM_VSI3_IRQn   227
+#define ARM_VSI4_IRQn   228
+#define ARM_VSI5_IRQn   229
+#define ARM_VSI6_IRQn   230
+#define ARM_VSI7_IRQn   231
+
+/// Structure type to access the virtual streaming interface
+typedef struct
+{
+  /// Interrupt Request (IRQ)
+  struct {
+    __IOM uint32_t Enable;      /*!< (R/W) IRQ Enable */
+    __OM  uint32_t Set;         /*!< (-/W) IRQ Set */
+    __OM  uint32_t Clear;       /*!< (-/W) IRQ Clear */
+    __IM  uint32_t Status;      /*!< (R/-) IRQ Status */
+  } IRQ;
+  uint32_t reserved1[60];
+  /// Time counter with 1MHz input frequency
+  struct {
+    __IOM uint32_t Control;     /*!< (R/W) Timer Control */
+    __IOM uint32_t Interval;    /*!< (R/W) Timer Interval Value (in microseconds) */
+    __IM  uint32_t Count;       /*!< (R/-) Timer Overflow Count */
+  } Timer;
+  uint32_t reserved2[61];
+  /// Direct Memory Access (DMA) Controller
+  struct {
+    __IOM uint32_t Control;     /*!< (R/W) DMA Control */
+    __IOM uint32_t Address;     /*!< (R/W) DMA Memory Start Address */
+    __IOM uint32_t BlockSize;   /*!< (R/W) DMA Block Size (in bytes, multiple of 4) */
+    __IOM uint32_t BlockNum;    /*!< (R/W) DMA Number of Blocks (must be 2^n) */
+    __IM  uint32_t BlockIndex;  /*!< (R/-) DMA Block Index */
+  } DMA;
+  uint32_t reserved3[59];
+  __IOM uint32_t Regs[64];      /*!< (R/W) User Registers */
+} ARM_VSI_Type;
+
+/* VSI Timer Control Definitions for Timer.Control register */
+#define ARM_VSI_Timer_Run_Pos           0U                                      /*!< Timer Control: Run Position */
+#define ARM_VSI_Timer_Run_Msk           (1UL << ARM_VSI_Timer_Run_Pos)          /*!< Timer Control: Run Mask */
+#define ARM_VSI_Timer_Periodic_Pos      1U                                      /*!< Timer Control: Periodic Position */
+#define ARM_VSI_Timer_Periodic_Msk      (1UL << ARM_VSI_Timer_Periodic_Pos)     /*!< Timer Control: Periodic Mask */
+#define ARM_VSI_Timer_Trig_IRQ_Pos      2U                                      /*!< Timer Control: Trig_IRQ Position */
+#define ARM_VSI_Timer_Trig_IRQ_Msk      (1UL << ARM_VSI_Timer_Trig_IRQ_Pos)     /*!< Timer Control: Trig_IRQ Mask */
+#define ARM_VSI_Timer_Trig_DMA_Pos      3U                                      /*!< Timer Control: Trig_DAM Position */
+#define ARM_VSI_Timer_Trig_DMA_Msk      (1UL << ARM_VSI_Timer_Trig_DMA_Pos)     /*!< Timer Control: Trig_DMA Mask */
+
+/* VSI DMA Control Definitions for DMA.Control register */
+#define ARM_VSI_DMA_Enable_Pos          0U                                      /*!< DMA Control: Enable Position */
+#define ARM_VSI_DMA_Enable_Msk          (1UL << ARM_VSI_DMA_Enable_Pos)         /*!< DMA Control: Enable Mask */
+#define ARM_VSI_DMA_Direction_Pos       1U                                      /*!< DMA Control: Direction Position */
+#define ARM_VSI_DMA_Direction_Msk       (1UL << ARM_VSI_DMA_Direction_Pos)      /*!< DMA Control: Direction Mask */
+#define ARM_VSI_DMA_Direction_P2M       (0UL*ARM_VSI_DMA_Direction_Msk)         /*!< DMA Control: Direction P2M */
+#define ARM_VSI_DMA_Direction_M2P       (1UL*ARM_VSI_DMA_Direction_Msk)         /*!< DMA Control: Direction M2P */
+
+/* Memory mapping of 8 VSI peripherals */
+#define ARM_VSI0_BASE           (0x4FF00000UL)                          /*!< VSI 0 Base Address */
+#define ARM_VSI1_BASE           (0x4FF10000UL)                          /*!< VSI 1 Base Address */
+#define ARM_VSI2_BASE           (0x4FF20000UL)                          /*!< VSI 2 Base Address */
+#define ARM_VSI3_BASE           (0x4FF30000UL)                          /*!< VSI 3 Base Address */
+#define ARM_VSI4_BASE           (0x4FF40000UL)                          /*!< VSI 4 Base Address */
+#define ARM_VSI5_BASE           (0x4FF50000UL)                          /*!< VSI 5 Base Address */
+#define ARM_VSI6_BASE           (0x4FF60000UL)                          /*!< VSI 6 Base Address */
+#define ARM_VSI7_BASE           (0x4FF70000UL)                          /*!< VSI 7 Base Address */
+#define ARM_VSI0                ((ARM_VSI_Type *)ARM_VSI0_BASE)         /*!< VSI 0 struct */
+#define ARM_VSI1                ((ARM_VSI_Type *)ARM_VSI1_BASE)         /*!< VSI 1 struct */
+#define ARM_VSI2                ((ARM_VSI_Type *)ARM_VSI2_BASE)         /*!< VSI 2 struct */
+#define ARM_VSI3                ((ARM_VSI_Type *)ARM_VSI3_BASE)         /*!< VSI 3 struct */
+#define ARM_VSI4                ((ARM_VSI_Type *)ARM_VSI4_BASE)         /*!< VSI 4 struct */
+#define ARM_VSI5                ((ARM_VSI_Type *)ARM_VSI5_BASE)         /*!< VSI 5 struct */
+#define ARM_VSI6                ((ARM_VSI_Type *)ARM_VSI6_BASE)         /*!< VSI 6 struct */
+#define ARM_VSI7                ((ARM_VSI_Type *)ARM_VSI7_BASE)         /*!< VSI 7 struct */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ARM_VSI_H */
diff --git a/source/hal/source/components/vsi/include/video_drv.h b/source/hal/source/components/vsi/include/video_drv.h
new file mode 100644
index 0000000..73a6ef7
--- /dev/null
+++ b/source/hal/source/components/vsi/include/video_drv.h
@@ -0,0 +1,142 @@
+/*
+* SPDX-FileCopyrightText: Copyright 2024 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
+*
+*     http://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.
+*/
+
+#ifndef VIDEO_DRV_H
+#define VIDEO_DRV_H
+
+#ifdef  __cplusplus
+extern "C"
+{
+#endif
+
+#include <stdint.h>
+
+/* Video Channel */
+#define VIDEO_DRV_IN0                   (0UL)       ///< Video Input channel 0
+#define VIDEO_DRV_OUT0                  (1UL)       ///< Video Output channel 0
+#define VIDEO_DRV_IN1                   (2UL)       ///< Video Input channel 1
+#define VIDEO_DRV_OUT1                  (3UL)       ///< Video Output channel 1
+
+/* Video Event */
+#define VIDEO_DRV_EVENT_FRAME           (1UL << 0)  ///< Video frame received
+#define VIDEO_DRV_EVENT_OVERFLOW        (1UL << 1)  ///< Video buffer overflow
+#define VIDEO_DRV_EVENT_UNDERFLOW       (1UL << 2)  ///< Video buffer underflow
+#define VIDEO_DRV_EVENT_EOS             (1UL << 3)  ///< Video end of stream
+
+/* Video Mode */
+#define VIDEO_DRV_MODE_SINGLE           (0UL)       ///< Single frame
+#define VIDEO_DRV_MODE_CONTINUOS        (1UL)       ///< Continuos stream
+
+/* Return code */
+#define VIDEO_DRV_OK                    (0)         ///< Operation succeeded
+#define VIDEO_DRV_ERROR                 (-1)        ///< Unspecified error
+#define VIDEO_DRV_ERROR_PARAMETER       (-2)        ///< Parameter error
+
+/// Video Color Format
+#define COLOR_FORMAT_BEGIN              (0UL)
+#define COLOR_GRAYSCALE8                (1UL)
+#define COLOR_RGB888                    (2UL)
+#define COLOR_BGR565                    (3UL)
+#define COLOR_YUV420                    (4UL)
+#define COLOR_NV12                      (5UL)
+#define COLOR_NV21                      (6UL)
+#define COLOR_FORMAT_END                (7UL)
+
+/// Video Status
+typedef struct {
+  uint32_t active       :  1;           ///< Video stream active
+  uint32_t buf_empty    :  1;           ///< Video stream buffer empty
+  uint32_t buf_full     :  1;           ///< Video stream buffer full
+  uint32_t overflow     :  1;           ///< Video buffer overflow (cleared on GetStatus)
+  uint32_t underflow    :  1;           ///< Video buffer underflow (cleared on GetStatus)
+  uint32_t eos          :  1;           ///< Video end of stream (cleared on GetStatus)
+  uint32_t reserved     : 26;
+} VideoDrv_Status_t;
+
+/// \brief       Video Events callback function type.
+/// \param[in]   channel        channel number
+/// \param[in]   event          events notification mask
+/// \return      none
+typedef void (*VideoDrv_Event_t) (uint32_t channel, uint32_t event);
+
+/// \brief       Initialize Video Interface.
+/// \param[in]   cb_event pointer to \ref VideoDrv_Event_t
+/// \return      return code
+int32_t VideoDrv_Initialize (VideoDrv_Event_t cb_event);
+
+/// \brief       De-initialize Video Interface.
+/// \return      return code
+int32_t VideoDrv_Uninitialize (void);
+
+/// \brief       Set Video Interface file.
+/// \param[in]   channel        channel number
+/// \param[in]   name           video filename (pointer to NULL terminated string)
+/// \return      return code
+int32_t VideoDrv_SetFile (uint32_t channel, const char *name);
+
+/// \brief       Configure Video Interface.
+/// \param[in]   channel        channel number
+/// \param[in]   frame_width    frame width in pixels
+/// \param[in]   frame_height   frame height in pixels
+/// \param[in]   color_format   pixel color format
+/// \param[in]   frame_rate     frame rate (frames per second)
+/// \return      return code
+int32_t VideoDrv_Configure (uint32_t channel, uint32_t frame_width, uint32_t frame_height, uint32_t color_format, uint32_t frame_rate);
+
+/// \brief       Set Video Interface buffer.
+/// \param[in]   channel        channel number
+/// \param[in]   buf            pointer to buffer for video stream
+/// \param[in]   buf_size       video stream buffer size in bytes
+/// \return      return code
+int32_t VideoDrv_SetBuf (uint32_t channel, void *buf, uint32_t buf_size);
+
+/// \brief       Flush Video Interface buffer.
+/// \param[in]   channel        channel number
+/// \return      return code
+int32_t VideoDrv_FlushBuf (uint32_t channel);
+
+/// \brief       Start Stream on Video Interface.
+/// \param[in]   channel        channel number
+/// \param[in]   mode           stream mode
+/// \return      return code
+int32_t VideoDrv_StreamStart (uint32_t channel, uint32_t mode);
+
+/// \brief       Stop Stream on Video Interface.
+/// \param[in]   channel        channel number
+/// \return      return code
+int32_t VideoDrv_StreamStop (uint32_t channel);
+
+/// \brief       Get Video Frame buffer.
+/// \param[in]   channel        channel number
+/// \return      pointer to frame buffer
+void *VideoDrv_GetFrameBuf (uint32_t channel);
+
+/// \brief       Release Video Frame.
+/// \param[in]   channel        channel number
+/// \return      return code
+int32_t VideoDrv_ReleaseFrame (uint32_t channel);
+
+/// \brief       Get Video Interface status.
+/// \param[in]   channel        channel number
+/// \return      \ref VideoDrv_Status_t
+VideoDrv_Status_t VideoDrv_GetStatus (uint32_t channel);
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif  /* VIDEO_DRV_H */
diff --git a/source/hal/source/components/vsi/source/video_drv.c b/source/hal/source/components/vsi/source/video_drv.c
new file mode 100644
index 0000000..ab4e2e5
--- /dev/null
+++ b/source/hal/source/components/vsi/source/video_drv.c
@@ -0,0 +1,600 @@
+/*
+* SPDX-FileCopyrightText: Copyright 2024 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
+*
+*     http://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.
+*/
+
+#include <stdint.h>
+#include <string.h>
+#include "video_drv.h"
+
+#include "RTE_Components.h"
+#include "arm_vsi.h"
+
+// Video channel definitions
+#ifndef VIDEO_INPUT_CHANNELS
+#define VIDEO_INPUT_CHANNELS    1
+#endif
+#if    (VIDEO_INPUT_CHANNELS > 2)
+#error "Maximum 2 Video Input channels are supported!"
+#endif
+#ifndef VIDEO_OUTPUT_CHANNELS
+#define VIDEO_OUTPUT_CHANNELS   1
+#endif
+#if    (VIDEO_OUTPUT_CHANNELS > 2)
+#error "Maximum 2 Video Output channels are supported!"
+#endif
+
+
+// Video peripheral definitions
+#define VideoI0                 ARM_VSI4                // Video Input channel 0 access struct
+#define VideoI0_IRQn            ARM_VSI4_IRQn           // Video Input channel 0 Interrupt number
+#define VideoI0_Handler         ARM_VSI4_Handler        // Video Input channel 0 Interrupt handler
+#define VideoI1                 ARM_VSI5                // Video Input channel 1 access struct
+#define VideoI1_IRQn            ARM_VSI5_IRQn           // Video Input channel 1 Interrupt number
+#define VideoI1_Handler         ARM_VSI5_Handler        // Video Input channel 1 Interrupt handler
+#define VideoO0                 ARM_VSI6                // Video Output channel 0 access struct
+#define VideoO0_IRQn            ARM_VSI6_IRQn           // Video Output channel 0 Interrupt number
+#define VideoO0_Handler         ARM_VSI6_Handler        // Video Output channel 0 Interrupt handler
+#define VideoO1                 ARM_VSI7                // Video Output channel 1 access struct
+#define VideoO1_IRQn            ARM_VSI7_IRQn           // Video Output channel 1 Interrupt number
+#define VideoO1_Handler         ARM_VSI7_Handler        // Video Output channel 1 Interrupt handler
+
+// Video Peripheral registers
+#define Reg_MODE                Regs[0]  // Mode: 0=Input, 1=Output
+#define Reg_CONTROL             Regs[1]  // Control: enable, continuos, flush
+#define Reg_STATUS              Regs[2]  // Status: active, buf_empty, buf_full, overflow, underflow, eos
+#define Reg_FILENAME_LEN        Regs[3]  // Filename length
+#define Reg_FILENAME_CHAR       Regs[4]  // Filename character
+#define Reg_FILENAME_VALID      Regs[5]  // Filename valid flag
+#define Reg_FRAME_WIDTH         Regs[6]  // Requested frame width
+#define Reg_FRAME_HEIGHT        Regs[7]  // Requested frame height
+#define Reg_COLOR_FORMAT        Regs[8]  // Color format
+#define Reg_FRAME_RATE          Regs[9]  // Frame rate
+#define Reg_FRAME_INDEX         Regs[10] // Frame index
+#define Reg_FRAME_COUNT         Regs[11] // Frame count
+#define Reg_FRAME_COUNT_MAX     Regs[12] // Frame count maximum
+
+// Video MODE register defintions
+#define Reg_MODE_IO_Pos                 0U
+#define Reg_MODE_IO_Msk                 (1UL << Reg_MODE_IO_Pos)
+#define Reg_MODE_Input                  (0UL << Reg_MODE_IO_Pos)
+#define Reg_MODE_Output                 (1UL << Reg_MODE_IO_Pos)
+
+// Video CONTROL register definitions
+#define Reg_CONTROL_ENABLE_Pos          0U
+#define Reg_CONTROL_ENABLE_Msk          (1UL << Reg_CONTROL_ENABLE_Pos)
+#define Reg_CONTROL_CONTINUOS_Pos       1U
+#define Reg_CONTROL_CONTINUOS_Msk       (1UL << Reg_CONTROL_CONTINUOS_Pos)
+#define Reg_CONTROL_BUF_FLUSH_Pos       2U
+#define Reg_CONTROL_BUF_FLUSH_Msk       (1UL << Reg_CONTROL_BUF_FLUSH_Pos)
+
+// Video STATUS register definitions
+#define Reg_STATUS_ACTIVE_Pos           0U
+#define Reg_STATUS_ACTIVE_Msk           (1UL << Reg_STATUS_ACTIVE_Pos)
+#define Reg_STATUS_BUF_EMPTY_Pos        1U
+#define Reg_STATUS_BUF_EMPTY_Msk        (1UL << Reg_STATUS_BUF_EMPTY_Pos)
+#define Reg_STATUS_BUF_FULL_Pos         2U
+#define Reg_STATUS_BUF_FULL_Msk         (1UL << Reg_STATUS_BUF_FULL_Pos)
+#define Reg_STATUS_OVERFLOW_Pos         3U
+#define Reg_STATUS_OVERFLOW_Msk         (1UL << Reg_STATUS_OVERFLOW_Pos)
+#define Reg_STATUS_UNDERFLOW_Pos        4U
+#define Reg_STATUS_UNDERFLOW_Msk        (1UL << Reg_STATUS_UNDERFLOW_Pos)
+#define Reg_STATUS_EOS_Pos              5U
+#define Reg_STATUS_EOS_Msk              (1UL << Reg_STATUS_EOS_Pos)
+
+// IRQ Status register definitions
+#define Reg_IRQ_Status_FRAME_Pos        0U
+#define Reg_IRQ_Status_FRAME_Msk        (1UL << Reg_IRQ_Status_FRAME_Pos)
+#define Reg_IRQ_Status_OVERFLOW_Pos     1U
+#define Reg_IRQ_Status_OVERFLOW_Msk     (1UL << Reg_IRQ_Status_OVERFLOW_Pos)
+#define Reg_IRQ_Status_UNDERFLOW_Pos    2U
+#define Reg_IRQ_Status_UNDERFLOW_Msk    (1UL << Reg_IRQ_Status_UNDERFLOW_Pos)
+#define Reg_IRQ_Status_EOS_Pos          3U
+#define Reg_IRQ_Status_EOS_Msk          (1UL << Reg_IRQ_Status_EOS_Pos)
+
+#define Reg_IRQ_Status_Msk              Reg_IRQ_Status_FRAME_Msk     | \
+                                        Reg_IRQ_Status_OVERFLOW_Msk  | \
+                                        Reg_IRQ_Status_UNDERFLOW_Msk | \
+                                        Reg_IRQ_Status_EOS_Msk
+
+// Video peripheral access structure
+static ARM_VSI_Type * const pVideo[4] = { VideoI0, VideoO0, VideoI1, VideoO1 };
+
+// Driver State
+static uint8_t  Initialized = 0U;
+static uint8_t  Configured[4] = { 0U, 0U, 0U, 0U };
+
+// Event Callback
+static VideoDrv_Event_t CB_Event = NULL;
+
+// Video Interrupt Handler
+static void Video_Handler (uint32_t channel) {
+  uint32_t irq_status;
+  uint32_t event;
+
+  irq_status = pVideo[channel]->IRQ.Status;
+  pVideo[channel]->IRQ.Clear = irq_status;
+  __DSB();
+  __ISB();
+
+  event = 0U;
+  if (irq_status & Reg_IRQ_Status_FRAME_Msk) {
+    event |= VIDEO_DRV_EVENT_FRAME;
+  }
+  if (irq_status & Reg_IRQ_Status_OVERFLOW_Msk) {
+    event |= VIDEO_DRV_EVENT_OVERFLOW;
+  }
+  if (irq_status & Reg_IRQ_Status_UNDERFLOW_Msk) {
+    event |= VIDEO_DRV_EVENT_UNDERFLOW;
+  }
+  if (irq_status & Reg_IRQ_Status_EOS_Msk) {
+    event |= VIDEO_DRV_EVENT_EOS;
+  }
+
+  if (CB_Event != NULL) {
+    CB_Event(channel, event);
+  }
+}
+
+// Video channel 0 Interrupt Handler
+#if (VIDEO_INPUT_CHANNELS >= 1)
+void VideoI0_Handler (void);
+void VideoI0_Handler (void) {
+  Video_Handler(0U);
+}
+#endif
+
+// Video channel 1 Interrupt Handler
+#if (VIDEO_OUTPUT_CHANNELS >= 1)
+void VideoO0_Handler (void);
+void VideoO0_Handler (void) {
+  Video_Handler(1U);
+}
+#endif
+
+// Video channel 2 Interrupt Handler
+#if (VIDEO_INPUT_CHANNELS >= 2)
+void VideoI1_Handler (void);
+void VideoI1_Handler (void) {
+  Video_Handler(2U);
+}
+#endif
+
+// Video channel 3 Interrupt Handler
+#if (VIDEO_OUTPUT_CHANNELS >= 2)
+void VideoO1_Handler (void);
+void VideoO1_Handler (void) {
+  Video_Handler(3U);
+}
+#endif
+
+// Initialize Video Interface
+int32_t VideoDrv_Initialize (VideoDrv_Event_t cb_event) {
+
+  CB_Event = cb_event;
+
+  // Initialize Video Input channel 0
+  #if (VIDEO_INPUT_CHANNELS >= 1)
+    VideoI0->Timer.Control = 0U;
+    VideoI0->DMA.Control   = 0U;
+    VideoI0->IRQ.Clear     = Reg_IRQ_Status_Msk;
+    VideoI0->IRQ.Enable    = Reg_IRQ_Status_Msk;
+    VideoI0->Reg_MODE      = Reg_MODE_Input;
+    VideoI0->Reg_CONTROL   = 0U;
+//  NVIC_EnableIRQ(VideoI0_IRQn);
+    NVIC->ISER[(((uint32_t)VideoI0_IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)VideoI0_IRQn) & 0x1FUL));
+  #endif
+  Configured[0] = 0U;
+
+  // Initialize Video Output channel 0
+  #if (VIDEO_OUTPUT_CHANNELS >= 1)
+    VideoO0->Timer.Control = 0U;
+    VideoO0->DMA.Control   = 0U;
+    VideoO0->IRQ.Clear     = Reg_IRQ_Status_Msk;
+    VideoO0->IRQ.Enable    = Reg_IRQ_Status_Msk;
+    VideoO0->Reg_MODE      = Reg_MODE_Output;
+    VideoO0->Reg_CONTROL   = 0U;
+//  NVIC_EnableIRQ(VideoO0_IRQn);
+    NVIC->ISER[(((uint32_t)VideoO0_IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)VideoO0_IRQn) & 0x1FUL));
+  #endif
+  Configured[1] = 0U;
+
+  // Initialize Video Input channel 1
+  #if (VIDEO_INPUT_CHANNELS >= 2)
+    VideoI1->Timer.Control = 0U;
+    VideoI1->DMA.Control   = 0U;
+    VideoI1->IRQ.Clear     = Reg_IRQ_Status_Msk;
+    VideoI1->IRQ.Enable    = Reg_IRQ_Status_Msk;
+    VideoI1->Reg_MODE      = Reg_MODE_Input;
+    VideoI1->Reg_CONTROL   = 0U;
+//  NVIC_EnableIRQ(VideoI1_IRQn);
+    NVIC->ISER[(((uint32_t)VideoI1_IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)VideoI1_IRQn) & 0x1FUL));
+  #endif
+  Configured[2] = 0U;
+
+  // Initialize Video Output channel 1
+  #if (VIDEO_OUTPUT_CHANNELS >= 2)
+    VideoO1->Timer.Control = 0U;
+    VideoO1->DMA.Control   = 0U;
+    VideoO1->IRQ.Clear     = Reg_IRQ_Status_Msk;
+    VideoO1->IRQ.Enable    = Reg_IRQ_Status_Msk;
+    VideoO1->Reg_MODE      = Reg_MODE_Output;
+    VideoO1->Reg_CONTROL   = 0U;
+//  NVIC_EnableIRQ(VideoO1_IRQn);
+    NVIC->ISER[(((uint32_t)VideoO1_IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)VideoO1_IRQn) & 0x1FUL));
+  #endif
+  Configured[3] = 0U;
+
+  __DSB();
+  __ISB();
+
+  Initialized = 1U;
+
+  return VIDEO_DRV_OK;
+}
+
+// De-initialize Video Interface
+int32_t VideoDrv_Uninitialize (void) {
+
+  // De-initialize Video Input channel 0
+  #if (VIDEO_INPUT_CHANNELS >= 1)
+//  NVIC_DisableIRQ(VideoI0_IRQn);
+    NVIC->ICER[(((uint32_t)VideoI0_IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)VideoI0_IRQn) & 0x1FUL));
+    VideoI0->Timer.Control = 0U;
+    VideoI0->DMA.Control   = 0U;
+    VideoI0->IRQ.Clear     = Reg_IRQ_Status_Msk;
+    VideoI0->IRQ.Enable    = 0U;
+    VideoI0->Reg_CONTROL   = 0U;
+  #endif
+
+  // De-initialize Video Output channel 0
+  #if (VIDEO_OUTPUT_CHANNELS >= 1)
+//  NVIC_DisableIRQ(VideoO0_IRQn);
+    NVIC->ICER[(((uint32_t)VideoO0_IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)VideoO0_IRQn) & 0x1FUL));
+    VideoO0->Timer.Control = 0U;
+    VideoO0->DMA.Control   = 0U;
+    VideoO0->IRQ.Clear     = Reg_IRQ_Status_Msk;
+    VideoO0->IRQ.Enable    = 0U;
+    VideoO0->Reg_CONTROL   = 0U;
+  #endif
+
+  // De-initialize Video Input channel 1
+  #if (VIDEO_INPUT_CHANNELS >= 2)
+//  NVIC_DisableIRQ(VideoI1_IRQn);
+    NVIC->ICER[(((uint32_t)VideoI1_IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)VideoI1_IRQn) & 0x1FUL));
+    VideoI1->Timer.Control = 0U;
+    VideoI1->DMA.Control   = 0U;
+    VideoI1->IRQ.Clear     = Reg_IRQ_Status_Msk;
+    VideoI1->IRQ.Enable    = 0U;
+    VideoI1->Reg_CONTROL   = 0U;
+  #endif
+
+  // De-initialize Video Output channel 1
+  #if (VIDEO_OUTPUT_CHANNELS >= 2)
+//  NVIC_DisableIRQ(VideoO1_IRQn);
+    NVIC->ICER[(((uint32_t)VideoO1_IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)VideoO1_IRQn) & 0x1FUL));
+    VideoO1->Timer.Control = 0U;
+    VideoO1->DMA.Control   = 0U;
+    VideoO1->IRQ.Clear     = Reg_IRQ_Status_Msk;
+    VideoO1->IRQ.Enable    = 0U;
+    VideoO1->Reg_CONTROL   = 0U;
+  #endif
+
+  __DSB();
+  __ISB();
+
+  Initialized = 0U;
+
+  return VIDEO_DRV_OK;
+}
+
+// Set Video Interface file
+int32_t VideoDrv_SetFile (uint32_t channel, const char *name) {
+  const char    *p;
+        uint32_t n;
+
+  if ((((channel & 1U) == 0U) && ((channel >> 1) >= VIDEO_INPUT_CHANNELS))  ||
+      (((channel & 1U) != 0U) && ((channel >> 1) >= VIDEO_OUTPUT_CHANNELS)) ||
+      (name == NULL)) {
+    return VIDEO_DRV_ERROR_PARAMETER;
+  }
+
+  if (Initialized == 0U) {
+    return VIDEO_DRV_ERROR;
+  }
+
+  if ((pVideo[channel]->Reg_STATUS & Reg_STATUS_ACTIVE_Msk) != 0U) {
+    return VIDEO_DRV_ERROR;
+  }
+
+  // Register Video filename
+  n = strlen(name);
+  pVideo[channel]->Reg_FILENAME_LEN = n;
+  for (p = name; n != 0U; n--) {
+    pVideo[channel]->Reg_FILENAME_CHAR = *p++;
+  }
+  if (pVideo[channel]->Reg_FILENAME_VALID == 0U) {
+    return VIDEO_DRV_ERROR;
+  }
+
+  return VIDEO_DRV_OK;
+}
+
+// Configure Video Interface
+int32_t VideoDrv_Configure (uint32_t channel, uint32_t frame_width, uint32_t frame_height, uint32_t color_format, uint32_t frame_rate) {
+  uint32_t pixel_size;
+  uint32_t block_size;
+
+  if ((((channel & 1U) == 0U) && ((channel >> 1) >= VIDEO_INPUT_CHANNELS))  ||
+      (((channel & 1U) != 0U) && ((channel >> 1) >= VIDEO_OUTPUT_CHANNELS)) ||
+      (frame_width  == 0U) ||
+      (frame_height == 0U) ||
+      (frame_rate   == 0U) ||
+      (color_format <= COLOR_FORMAT_BEGIN) ||
+      (color_format >= COLOR_FORMAT_END)) {
+    return VIDEO_DRV_ERROR_PARAMETER;
+  }
+
+  switch (color_format) {
+    case COLOR_GRAYSCALE8:
+      pixel_size = 8U;
+      break;
+    case COLOR_YUV420:
+      pixel_size = 12U;
+      break;
+    case COLOR_BGR565:
+      pixel_size = 16U;
+      break;
+    case COLOR_RGB888:
+    case COLOR_NV12:
+    case COLOR_NV21:
+      pixel_size = 24U;
+      break;
+    default:
+      return VIDEO_DRV_ERROR_PARAMETER;
+  }
+
+  block_size = (((frame_width * frame_height) * pixel_size) + 7U) / 8U;
+  block_size = (block_size + 3U) & ~3U;
+
+  if (Initialized == 0U) {
+    return VIDEO_DRV_ERROR;
+  }
+
+  if ((pVideo[channel]->Reg_STATUS & Reg_STATUS_ACTIVE_Msk) != 0U) {
+    return VIDEO_DRV_ERROR;
+  }
+
+  pVideo[channel]->Reg_FRAME_WIDTH  = frame_width;
+  pVideo[channel]->Reg_FRAME_HEIGHT = frame_height;
+  pVideo[channel]->Reg_COLOR_FORMAT = color_format;
+  pVideo[channel]->Reg_FRAME_RATE   = frame_rate;
+  pVideo[channel]->Timer.Interval   = 1000000U / frame_rate;
+  pVideo[channel]->DMA.BlockSize    = block_size;
+
+  Configured[channel] = 1U;
+
+  return VIDEO_DRV_OK;
+}
+
+// Set Video Interface buffer
+int32_t VideoDrv_SetBuf (uint32_t channel, void *buf, uint32_t buf_size) {
+  uint32_t block_num;
+
+  if ((((channel & 1U) == 0U) && ((channel >> 1) >= VIDEO_INPUT_CHANNELS))  ||
+      (((channel & 1U) != 0U) && ((channel >> 1) >= VIDEO_OUTPUT_CHANNELS)) ||
+      (buf      == NULL) ||
+      (buf_size == 0U)) {
+    return VIDEO_DRV_ERROR_PARAMETER;
+  }
+
+  if ((Initialized         == 0U) ||
+      (Configured[channel] == 0U)) {
+    return VIDEO_DRV_ERROR;
+  }
+
+  if ((pVideo[channel]->Reg_STATUS & Reg_STATUS_ACTIVE_Msk) != 0U) {
+    return VIDEO_DRV_ERROR;
+  }
+
+  block_num = buf_size / pVideo[channel]->DMA.BlockSize;
+  if (block_num == 0U) {
+    return VIDEO_DRV_ERROR;
+  }
+
+  pVideo[channel]->Reg_FRAME_COUNT_MAX = block_num;
+  pVideo[channel]->DMA.BlockNum        = block_num;
+
+  pVideo[channel]->DMA.Address = (uint32_t)buf;
+
+  Configured[channel] = 2U;
+
+  return VIDEO_DRV_OK;
+}
+
+// Flush Video Interface buffer
+int32_t VideoDrv_FlushBuf (uint32_t channel) {
+
+  if ((((channel & 1U) == 0U) && ((channel >> 1) >= VIDEO_INPUT_CHANNELS))  ||
+      (((channel & 1U) != 0U) && ((channel >> 1) >= VIDEO_OUTPUT_CHANNELS))) {
+    return VIDEO_DRV_ERROR_PARAMETER;
+  }
+
+  if (Initialized == 0U) {
+    return VIDEO_DRV_ERROR;
+  }
+
+  if ((pVideo[channel]->Reg_STATUS & Reg_STATUS_ACTIVE_Msk) != 0U) {
+    return VIDEO_DRV_ERROR;
+  }
+
+  pVideo[channel]->Reg_CONTROL = Reg_CONTROL_BUF_FLUSH_Msk;
+
+  return VIDEO_DRV_OK;
+}
+
+// Start Stream on Video Interface
+int32_t VideoDrv_StreamStart (uint32_t channel, uint32_t mode) {
+  uint32_t control;
+
+  if ((((channel & 1U) == 0U) && ((channel >> 1) >= VIDEO_INPUT_CHANNELS))  ||
+      (((channel & 1U) != 0U) && ((channel >> 1) >= VIDEO_OUTPUT_CHANNELS)) ||
+      (mode > VIDEO_DRV_MODE_CONTINUOS)) {
+    return VIDEO_DRV_ERROR_PARAMETER;
+  }
+
+  if ((Initialized         == 0U) ||
+      (Configured[channel] <  2U)) {
+    return VIDEO_DRV_ERROR;
+  }
+
+  if ((pVideo[channel]->Reg_STATUS & Reg_STATUS_ACTIVE_Msk) != 0U) {
+    return VIDEO_DRV_OK;
+  }
+
+  control = Reg_CONTROL_ENABLE_Msk;
+  if (mode == VIDEO_DRV_MODE_CONTINUOS) {
+    control |= Reg_CONTROL_CONTINUOS_Msk;
+  }
+  pVideo[channel]->Reg_CONTROL = control;
+
+
+  if ((pVideo[channel]->Reg_STATUS & Reg_STATUS_ACTIVE_Msk) == 0U) {
+    return VIDEO_DRV_ERROR;
+  }
+
+  control = ARM_VSI_DMA_Enable_Msk;
+  if ((channel & 1U) == 0U) {
+    control |= ARM_VSI_DMA_Direction_P2M;
+  } else {
+    control |= ARM_VSI_DMA_Direction_M2P;
+  }
+  pVideo[channel]->DMA.Control = control;
+
+  control = ARM_VSI_Timer_Run_Msk      |
+            ARM_VSI_Timer_Trig_DMA_Msk |
+            ARM_VSI_Timer_Trig_IRQ_Msk;
+  if (mode == VIDEO_DRV_MODE_CONTINUOS) {
+    control |= ARM_VSI_Timer_Periodic_Msk;
+  }
+  pVideo[channel]->Timer.Control = control;
+
+  return VIDEO_DRV_OK;
+}
+
+// Stop Stream on Video Interface
+int32_t VideoDrv_StreamStop (uint32_t channel) {
+
+  if ((((channel & 1U) == 0U) && ((channel >> 1) >= VIDEO_INPUT_CHANNELS))  ||
+      (((channel & 1U) != 0U) && ((channel >> 1) >= VIDEO_OUTPUT_CHANNELS))) {
+    return VIDEO_DRV_ERROR_PARAMETER;
+  }
+
+  if ((Initialized         == 0U) ||
+      (Configured[channel] <  2U)) {
+    return VIDEO_DRV_ERROR;
+  }
+
+  if ((pVideo[channel]->Reg_STATUS & Reg_STATUS_ACTIVE_Msk) == 0U) {
+    return VIDEO_DRV_OK;
+  }
+
+  pVideo[channel]->Timer.Control = 0U;
+  pVideo[channel]->DMA.Control   = 0U;
+  pVideo[channel]->Reg_CONTROL   = 0U;
+
+  return VIDEO_DRV_OK;
+}
+
+// Get Video Frame buffer
+void *VideoDrv_GetFrameBuf (uint32_t channel) {
+  void *frame = NULL;
+
+  if ((((channel & 1U) == 0U) && ((channel >> 1) >= VIDEO_INPUT_CHANNELS))  ||
+      (((channel & 1U) != 0U) && ((channel >> 1) >= VIDEO_OUTPUT_CHANNELS))) {
+    return NULL;
+  }
+
+  if ((Initialized         == 0U) ||
+      (Configured[channel] <  2U)) {
+    return NULL;
+  }
+
+  if ((pVideo[channel]->Reg_MODE & Reg_MODE_IO_Msk) == Reg_MODE_Input) {
+    // Input
+    if ((pVideo[channel]->Reg_STATUS & Reg_STATUS_BUF_EMPTY_Msk) != 0U) {
+      return NULL;
+    }
+  } else {
+    // Output
+    if ((pVideo[channel]->Reg_STATUS & Reg_STATUS_BUF_FULL_Msk) != 0U) {
+      return NULL;
+    }
+  }
+
+  frame = (void *)(pVideo[channel]->DMA.Address + (pVideo[channel]->Reg_FRAME_INDEX * pVideo[channel]->DMA.BlockSize));
+
+  return frame;
+}
+
+// Release Video Frame
+int32_t VideoDrv_ReleaseFrame (uint32_t channel) {
+
+  if ((((channel & 1U) == 0U) && ((channel >> 1) >= VIDEO_INPUT_CHANNELS))  ||
+      (((channel & 1U) != 0U) && ((channel >> 1) >= VIDEO_OUTPUT_CHANNELS))) {
+    return VIDEO_DRV_ERROR_PARAMETER;
+  }
+
+  if ((Initialized         == 0U) ||
+      (Configured[channel] <  2U)) {
+    return VIDEO_DRV_ERROR;
+  }
+
+  if ((pVideo[channel]->Reg_MODE & Reg_MODE_IO_Msk) == Reg_MODE_Input) {
+    // Input
+    if ((pVideo[channel]->Reg_STATUS & Reg_STATUS_BUF_EMPTY_Msk) != 0U) {
+      return VIDEO_DRV_ERROR;
+    }
+  } else {
+    // Output
+    if ((pVideo[channel]->Reg_STATUS & Reg_STATUS_BUF_FULL_Msk) != 0U) {
+      return VIDEO_DRV_ERROR;
+    }
+  }
+
+  pVideo[channel]->Reg_FRAME_INDEX = 0U;
+
+  return VIDEO_DRV_OK;
+}
+
+
+// Get Video Interface status
+VideoDrv_Status_t VideoDrv_GetStatus (uint32_t channel) {
+  VideoDrv_Status_t status = { 0U, 0U, 0U, 0U, 0U, 0U, 0U };
+  uint32_t          status_reg;
+
+  if ((((channel & 1U) == 0U) && ((channel >> 1) < VIDEO_INPUT_CHANNELS))  ||
+      (((channel & 1U) != 0U) && ((channel >> 1) < VIDEO_OUTPUT_CHANNELS))) {
+    status_reg       =  pVideo[channel]->Reg_STATUS;
+    status.active    = (status_reg & Reg_STATUS_ACTIVE_Msk)    >> Reg_STATUS_ACTIVE_Pos;
+    status.buf_empty = (status_reg & Reg_STATUS_BUF_EMPTY_Msk) >> Reg_STATUS_BUF_EMPTY_Pos;
+    status.buf_full  = (status_reg & Reg_STATUS_BUF_FULL_Msk)  >> Reg_STATUS_BUF_FULL_Pos;
+    status.overflow  = (status_reg & Reg_STATUS_OVERFLOW_Msk)  >> Reg_STATUS_OVERFLOW_Pos;
+    status.underflow = (status_reg & Reg_STATUS_UNDERFLOW_Msk) >> Reg_STATUS_UNDERFLOW_Pos;
+    status.eos       = (status_reg & Reg_STATUS_EOS_Msk)       >> Reg_STATUS_EOS_Pos;
+  }
+
+  return status;
+}
diff --git a/source/hal/source/platform/mps3/CMakeLists.txt b/source/hal/source/platform/mps3/CMakeLists.txt
index 5008f0b..2791150 100644
--- a/source/hal/source/platform/mps3/CMakeLists.txt
+++ b/source/hal/source/platform/mps3/CMakeLists.txt
@@ -1,5 +1,5 @@
 #----------------------------------------------------------------------------
-#  SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+#  SPDX-FileCopyrightText: Copyright 2022, 2024 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");
@@ -133,6 +133,18 @@
     target_compile_definitions(${PLATFORM_DRIVERS_TARGET} PUBLIC CPU_PROFILE_ENABLED)
 endif()
 
+# If Virtual Streaming Interface is enabled, we need to link the vsi driver
+if (VSI_ENABLED)
+    target_sources(${PLATFORM_DRIVERS_TARGET}
+            PRIVATE
+            source/vsi_mps3.c)
+    add_subdirectory(${COMPONENTS_DIR}/vsi ${CMAKE_BINARY_DIR}/vsi)
+
+    target_link_libraries(${PLATFORM_DRIVERS_TARGET}
+            PUBLIC
+            arm_vsi)
+endif ()
+
 # If Ethos-U is enabled, we need the driver library too
 if (ETHOS_U_NPU_ENABLED)
 
diff --git a/source/hal/source/platform/mps3/include/sse-300/peripheral_irqs.h b/source/hal/source/platform/mps3/include/sse-300/peripheral_irqs.h
index 2085c6a..9fb7b9e 100644
--- a/source/hal/source/platform/mps3/include/sse-300/peripheral_irqs.h
+++ b/source/hal/source/platform/mps3/include/sse-300/peripheral_irqs.h
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+ * SPDX-FileCopyrightText: Copyright 2022, 2024 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");
@@ -131,6 +131,14 @@
 #define UARTRX5_IRQn               (125)  /* UART 5 RX Interrupt                   */
 #define UARTTX5_IRQn               (126)  /* UART 5 TX Interrupt                   */
 #define UART5_IRQn                 (127)  /* UART 5 combined Interrupt             */
+#define ARM_VSI0_IRQn              (224)  /*  224: VSI 0 */
+#define ARM_VSI1_IRQn              (225)  /*  225: VSI 1 */
+#define ARM_VSI2_IRQn              (226)  /*  226: VSI 2 */
+#define ARM_VSI3_IRQn              (227)  /*  227: VSI 3 */
+#define ARM_VSI4_IRQn              (228)  /*  228: VSI 4 */
+#define ARM_VSI5_IRQn              (229)  /*  229: VSI 5 */
+#define ARM_VSI6_IRQn              (230)  /*  230: VSI 6 */
+#define ARM_VSI7_IRQn              (231)  /*  231: VSI 7 */
 /* #undef HDCLCD_IRQn */
 
 #endif /* PERIPHERAL_IRQS_H */
diff --git a/source/hal/source/platform/mps3/include/sse-310/peripheral_irqs.h b/source/hal/source/platform/mps3/include/sse-310/peripheral_irqs.h
index 95b62c7..0414f63 100644
--- a/source/hal/source/platform/mps3/include/sse-310/peripheral_irqs.h
+++ b/source/hal/source/platform/mps3/include/sse-310/peripheral_irqs.h
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+ * SPDX-FileCopyrightText: Copyright 2022, 2024 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");
@@ -129,6 +129,14 @@
 #define UARTRX5_IRQn               (125)  /* UART 5 RX Interrupt                   */
 #define UARTTX5_IRQn               (126)  /* UART 5 TX Interrupt                   */
 #define UART5_IRQn                 (127)  /* UART 5 combined Interrupt             */
+#define ARM_VSI0_IRQn              (224)  /*  224: VSI 0 */
+#define ARM_VSI1_IRQn              (225)  /*  225: VSI 1 */
+#define ARM_VSI2_IRQn              (226)  /*  226: VSI 2 */
+#define ARM_VSI3_IRQn              (227)  /*  227: VSI 3 */
+#define ARM_VSI4_IRQn              (228)  /*  228: VSI 4 */
+#define ARM_VSI5_IRQn              (229)  /*  229: VSI 5 */
+#define ARM_VSI6_IRQn              (230)  /*  230: VSI 6 */
+#define ARM_VSI7_IRQn              (231)  /*  231: VSI 7 */
 /* #undef HDCLCD_IRQn */
 
 #endif /* PERIPHERAL_IRQS_H */
diff --git a/source/hal/source/platform/mps3/include/vsi_mps3.h b/source/hal/source/platform/mps3/include/vsi_mps3.h
new file mode 100644
index 0000000..a8895ea
--- /dev/null
+++ b/source/hal/source/platform/mps3/include/vsi_mps3.h
@@ -0,0 +1,115 @@
+/*
+ * SPDX-FileCopyrightText: Copyright 2024 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
+ *
+ *     http://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.
+ */
+#ifndef VSI_MPS3_H
+#define VSI_MPS3_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "peripheral_irqs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef __IM
+#define __IM  volatile const    /*! Defines 'read only' structure member permissions */
+#endif
+#ifndef __OM
+#define __OM  volatile          /*! Defines 'write only' structure member permissions */
+#endif
+#ifndef __IOM
+#define __IOM volatile          /*! Defines 'read/write' structure member permissions */
+#endif
+
+/// Structure type to access the virtual streaming interface
+typedef struct
+{
+    /// Interrupt Request (IRQ)
+    struct {
+        __IOM uint32_t Enable;      /*!< (R/W) IRQ Enable */
+        __OM  uint32_t Set;         /*!< (-/W) IRQ Set */
+        __OM  uint32_t Clear;       /*!< (-/W) IRQ Clear */
+        __IM  uint32_t Status;      /*!< (R/-) IRQ Status */
+    } IRQ;
+    uint32_t reserved1[60];
+    /// Time counter with 1MHz input frequency
+    struct {
+        __IOM uint32_t Control;     /*!< (R/W) Timer Control */
+        __IOM uint32_t Interval;    /*!< (R/W) Timer Interval Value (in microseconds) */
+        __IM  uint32_t Count;       /*!< (R/-) Timer Overflow Count */
+    } Timer;
+    uint32_t reserved2[61];
+    /// Direct Memory Access (DMA) Controller
+    struct {
+        __IOM uint32_t Control;     /*!< (R/W) DMA Control */
+        __IOM uint32_t Address;     /*!< (R/W) DMA Memory Start Address */
+        __IOM uint32_t BlockSize;   /*!< (R/W) DMA Block Size (in bytes, multiple of 4) */
+        __IOM uint32_t BlockNum;    /*!< (R/W) DMA Number of Blocks (must be 2^n) */
+        __IM  uint32_t BlockIndex;  /*!< (R/-) DMA Block Index */
+    } DMA;
+    uint32_t reserved3[59];
+    __IOM uint32_t Regs[64];      /*!< (R/W) User Registers */
+} ARM_VSI_Type;
+
+/* VSI Timer Control Definitions for Timer.Control register */
+#define ARM_VSI_Timer_Run_Pos           0U                                      /*!< Timer Control: Run Position */
+#define ARM_VSI_Timer_Run_Msk           (1UL << ARM_VSI_Timer_Run_Pos)          /*!< Timer Control: Run Mask */
+#define ARM_VSI_Timer_Periodic_Pos      1U                                      /*!< Timer Control: Periodic Position */
+#define ARM_VSI_Timer_Periodic_Msk      (1UL << ARM_VSI_Timer_Periodic_Pos)     /*!< Timer Control: Periodic Mask */
+#define ARM_VSI_Timer_Trig_IRQ_Pos      2U                                      /*!< Timer Control: Trig_IRQ Position */
+#define ARM_VSI_Timer_Trig_IRQ_Msk      (1UL << ARM_VSI_Timer_Trig_IRQ_Pos)     /*!< Timer Control: Trig_IRQ Mask */
+#define ARM_VSI_Timer_Trig_DMA_Pos      3U                                      /*!< Timer Control: Trig_DAM Position */
+#define ARM_VSI_Timer_Trig_DMA_Msk      (1UL << ARM_VSI_Timer_Trig_DMA_Pos)     /*!< Timer Control: Trig_DMA Mask */
+
+/* VSI DMA Control Definitions for DMA.Control register */
+#define ARM_VSI_DMA_Enable_Pos          0U                                      /*!< DMA Control: Enable Position */
+#define ARM_VSI_DMA_Enable_Msk          (1UL << ARM_VSI_DMA_Enable_Pos)         /*!< DMA Control: Enable Mask */
+#define ARM_VSI_DMA_Direction_Pos       1U                                      /*!< DMA Control: Direction Position */
+#define ARM_VSI_DMA_Direction_Msk       (1UL << ARM_VSI_DMA_Direction_Pos)      /*!< DMA Control: Direction Mask */
+#define ARM_VSI_DMA_Direction_P2M       (0UL*ARM_VSI_DMA_Direction_Msk)         /*!< DMA Control: Direction P2M */
+#define ARM_VSI_DMA_Direction_M2P       (1UL*ARM_VSI_DMA_Direction_Msk)         /*!< DMA Control: Direction M2P */
+
+/* Memory mapping of 8 VSI peripherals */
+#define ARM_VSI0_BASE           (0x4FF00000UL)                          /*!< VSI 0 Base Address */
+#define ARM_VSI1_BASE           (0x4FF10000UL)                          /*!< VSI 1 Base Address */
+#define ARM_VSI2_BASE           (0x4FF20000UL)                          /*!< VSI 2 Base Address */
+#define ARM_VSI3_BASE           (0x4FF30000UL)                          /*!< VSI 3 Base Address */
+#define ARM_VSI4_BASE           (0x4FF40000UL)                          /*!< VSI 4 Base Address */
+#define ARM_VSI5_BASE           (0x4FF50000UL)                          /*!< VSI 5 Base Address */
+#define ARM_VSI6_BASE           (0x4FF60000UL)                          /*!< VSI 6 Base Address */
+#define ARM_VSI7_BASE           (0x4FF70000UL)                          /*!< VSI 7 Base Address */
+#define ARM_VSI0                ((ARM_VSI_Type *)ARM_VSI0_BASE)         /*!< VSI 0 struct */
+#define ARM_VSI1                ((ARM_VSI_Type *)ARM_VSI1_BASE)         /*!< VSI 1 struct */
+#define ARM_VSI2                ((ARM_VSI_Type *)ARM_VSI2_BASE)         /*!< VSI 2 struct */
+#define ARM_VSI3                ((ARM_VSI_Type *)ARM_VSI3_BASE)         /*!< VSI 3 struct */
+#define ARM_VSI4                ((ARM_VSI_Type *)ARM_VSI4_BASE)         /*!< VSI 4 struct */
+#define ARM_VSI5                ((ARM_VSI_Type *)ARM_VSI5_BASE)         /*!< VSI 5 struct */
+#define ARM_VSI6                ((ARM_VSI_Type *)ARM_VSI6_BASE)         /*!< VSI 6 struct */
+#define ARM_VSI7                ((ARM_VSI_Type *)ARM_VSI7_BASE)         /*!< VSI 7 struct */
+
+/**
+ * @brief   Initialises the VSI
+ * @return  0 if successful, error code otherwise
+ **/
+int vsi_init(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* VSI_MPS3_H */
diff --git a/source/hal/source/platform/mps3/source/platform_drivers.c b/source/hal/source/platform/mps3/source/platform_drivers.c
index 73b388b..1f55bb9 100644
--- a/source/hal/source/platform/mps3/source/platform_drivers.c
+++ b/source/hal/source/platform/mps3/source/platform_drivers.c
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: Copyright 2022-2023 Arm Limited and/or its affiliates <open-source-office@arm.com>
+ * SPDX-FileCopyrightText: Copyright 2022-2024 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");
@@ -40,6 +40,10 @@
 
 #endif /* ARM_NPU */
 
+#if VSI_ENABLED
+#include "vsi_mps3.h"
+#endif /* VSI_ENABLED */
+
 /**
  * @brief   Checks if the platform is valid by checking
  *          the CPU ID for the FPGA implementation against
@@ -94,6 +98,12 @@
 
 #endif /* ARM_NPU */
 
+#if VSI_ENABLED
+    if (0 != (err = vsi_init())) {
+        return err;
+    }
+#endif /* VSI_ENABLED */
+
     /* Print target design info */
     info("Target system design: %s\n", s_platform_name);
     return 0;
diff --git a/source/hal/source/platform/mps3/source/vsi_mps3.c b/source/hal/source/platform/mps3/source/vsi_mps3.c
new file mode 100644
index 0000000..8516c1c
--- /dev/null
+++ b/source/hal/source/platform/mps3/source/vsi_mps3.c
@@ -0,0 +1,62 @@
+/*
+ * SPDX-FileCopyrightText: Copyright 2024 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
+ *
+ *     http://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.
+ */
+#include "vsi_mps3.h"
+
+#include "log_macros.h"
+#include "RTE_Components.h"
+#include "peripheral_irqs.h"
+
+#include "video_drv.h"
+
+/* Exception / Interrupt Handler */
+#define DEFAULT_IRQ_HANDLER(handler_name)  \
+void __WEAK __NO_RETURN handler_name(void); \
+void handler_name(void) { \
+   while(1); \
+}
+
+DEFAULT_IRQ_HANDLER(ARM_VSI0_Handler)
+DEFAULT_IRQ_HANDLER(ARM_VSI1_Handler)
+DEFAULT_IRQ_HANDLER(ARM_VSI2_Handler)
+DEFAULT_IRQ_HANDLER(ARM_VSI3_Handler)
+DEFAULT_IRQ_HANDLER(ARM_VSI4_Handler)
+DEFAULT_IRQ_HANDLER(ARM_VSI5_Handler)
+DEFAULT_IRQ_HANDLER(ARM_VSI6_Handler)
+DEFAULT_IRQ_HANDLER(ARM_VSI7_Handler)
+
+int vsi_init(void)
+{
+    /* Register the VSI handlers in our vector table. */
+    NVIC_SetVector(ARM_VSI0_IRQn, (uint32_t)ARM_VSI0_Handler);
+    NVIC_SetVector(ARM_VSI1_IRQn, (uint32_t)ARM_VSI1_Handler);
+    NVIC_SetVector(ARM_VSI2_IRQn, (uint32_t)ARM_VSI2_Handler);
+    NVIC_SetVector(ARM_VSI3_IRQn, (uint32_t)ARM_VSI3_Handler);
+    NVIC_SetVector(ARM_VSI4_IRQn, (uint32_t)ARM_VSI4_Handler);
+    NVIC_SetVector(ARM_VSI5_IRQn, (uint32_t)ARM_VSI5_Handler);
+    NVIC_SetVector(ARM_VSI6_IRQn, (uint32_t)ARM_VSI6_Handler);
+    NVIC_SetVector(ARM_VSI7_IRQn, (uint32_t)ARM_VSI7_Handler);
+
+    /* Initialize Video VSI. */
+    if (VideoDrv_Initialize(NULL) != VIDEO_DRV_OK) {
+        printf_err("Failed to initialise video driver\n");
+        return 1;
+    }
+
+    info("VSI device initialised\n");
+
+    return 0;
+}
diff --git a/source/use_case/img_class/usecase.cmake b/source/use_case/img_class/usecase.cmake
index 4c25989..f18522c 100644
--- a/source/use_case/img_class/usecase.cmake
+++ b/source/use_case/img_class/usecase.cmake
@@ -1,5 +1,5 @@
 #----------------------------------------------------------------------------
-#  SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates <open-source-office@arm.com>
+#  SPDX-FileCopyrightText: Copyright 2021, 2024 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");
@@ -28,10 +28,12 @@
     FILEPATH)
 
 # Generate input files
-generate_images_code("${${use_case}_FILE_PATH}"
-                     ${SRC_GEN_DIR}
-                     ${INC_GEN_DIR}
-                     "${${use_case}_IMAGE_SIZE}")
+generate_images_code(
+    INPUT_DIR   "${${use_case}_FILE_PATH}"
+    SRC_OUT     ${SRC_GEN_DIR}
+    HDR_OUT     ${INC_GEN_DIR}
+    IMG_SIZE    "${${use_case}_IMAGE_SIZE}"
+)
 
 # Generate labels file
 set(${use_case}_LABELS_CPP_FILE Labels)
diff --git a/source/use_case/object_detection/include/UseCaseHandler.hpp b/source/use_case/object_detection/include/UseCaseHandler.hpp
index 9fe716f..43fb6bf 100644
--- a/source/use_case/object_detection/include/UseCaseHandler.hpp
+++ b/source/use_case/object_detection/include/UseCaseHandler.hpp
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+ * SPDX-FileCopyrightText: Copyright 2022, 2024 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");
@@ -17,11 +17,39 @@
 #ifndef OBJ_DET_HANDLER_HPP
 #define OBJ_DET_HANDLER_HPP
 
+#if VSI_ENABLED
+#include "DetectionResult.hpp"
+#include <vector>
+#endif
+
+#include <cstdint>
 #include "AppContext.hpp"
 
 namespace arm {
 namespace app {
 
+#if VSI_ENABLED
+    typedef object_detection::DetectionResult OdResults;
+
+    /**
+     * @brief       Draw boxe(s) detected by the model.
+     * @param[in]   image        Pointer to the image.
+     * @param[in]   imageWidth   Image width.
+     * @param[in]   imageHeight  Image height.
+     * @param[in]   results      A vector of detection results.
+     **/
+    void DrawDetectionBoxesVsi(uint8_t* image,
+                               const uint32_t imageWidth,
+                               const uint32_t imageHeight,
+                               const std::vector<OdResults>& results);
+    /**
+     * @brief       Handles the inference event when using VSI.
+     * @param[in]   ctx        Pointer to the application context.
+     * @return      true or false based on execution success.
+     **/
+    bool ObjectDetectionHandlerVsi(ApplicationContext& ctx);
+#endif
+
     /**
      * @brief       Handles the inference event.
      * @param[in]   ctx        Pointer to the application context.
diff --git a/source/use_case/object_detection/src/MainLoop.cc b/source/use_case/object_detection/src/MainLoop.cc
index 4735bcb..fe21de6 100644
--- a/source/use_case/object_detection/src/MainLoop.cc
+++ b/source/use_case/object_detection/src/MainLoop.cc
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+ * SPDX-FileCopyrightText: Copyright 2022, 2024 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");
@@ -41,7 +41,12 @@
     printf("  %u. Run detection ifm at chosen index\n", common::MENU_OPT_RUN_INF_CHOSEN);
     printf("  %u. Run detection on all ifm\n", common::MENU_OPT_RUN_INF_ALL);
     printf("  %u. Show NN model info\n", common::MENU_OPT_SHOW_MODEL_INFO);
-    printf("  %u. List ifm\n\n", common::MENU_OPT_LIST_IFM);
+    printf("  %u. List ifm\n", common::MENU_OPT_LIST_IFM);
+#if VSI_ENABLED
+    fflush(stdout);
+    printf("  %u. Run detection using VSI as input\n", common::MENU_OPT_RUN_INF_VSI);
+#endif
+    printf("\n");
     printf("  Choice: ");
     fflush(stdout);
 }
@@ -99,6 +104,11 @@
             case common::MENU_OPT_LIST_IFM:
                 executionSuccessful = ListFilesHandler(caseContext);
                 break;
+#if VSI_ENABLED
+            case common::MENU_OPT_RUN_INF_VSI:
+                executionSuccessful = ObjectDetectionHandlerVsi(caseContext);
+                break;
+#endif
             default:
                 printf("Incorrect choice, try again.");
                 break;
diff --git a/source/use_case/object_detection/src/UseCaseHandler.cc b/source/use_case/object_detection/src/UseCaseHandler.cc
index 9330187..1a20db5 100644
--- a/source/use_case/object_detection/src/UseCaseHandler.cc
+++ b/source/use_case/object_detection/src/UseCaseHandler.cc
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates
+ * SPDX-FileCopyrightText: Copyright 2022, 2024 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");
@@ -28,6 +28,235 @@
 namespace arm {
 namespace app {
 
+#if VSI_ENABLED
+#include "video_drv.h"                /* Video Driver API. */
+
+    /**
+     * @brief Draws a box in the image using the object detection result object.
+     *
+     * @param[out] imageData    Pointer to the start of the image.
+     * @param[in]  width        Image width.
+     * @param[in]  height       Image height.
+     * @param[in]  result       Object detection result.
+     */
+    static void DrawBox(uint8_t* imageData,
+                        const uint32_t width,
+                        const uint32_t height,
+                        const OdResults& result)
+    {
+        UNUSED(height);
+        const auto x = result.m_x0;
+        const auto y = result.m_y0;
+        const auto w = result.m_w;
+        const auto h = result.m_h;
+
+        const uint32_t step = width * 3;
+        uint8_t* const imStart = imageData + (y * step) + (x * 3);
+
+        uint8_t* dst_0 = imStart;
+        uint8_t* dst_1 = imStart + (h * step);
+
+        for (uint32_t i = 0; i < static_cast<uint32_t>(w); ++i) {
+            *dst_0 = 255;
+            *dst_1 = 255;
+
+            dst_0 += 3;
+            dst_1 += 3;
+        }
+
+        dst_0 = imStart;
+        dst_1 = imStart + (w * 3);
+
+        for (uint32_t j = 0; j < static_cast<uint32_t>(h); ++j) {
+            *dst_0 = 255;
+            *dst_1 = 255;
+
+            dst_0 += step;
+            dst_1 += step;
+        }
+    }
+
+    void DrawDetectionBoxesVsi(uint8_t* image,
+                               const uint32_t imageWidth,
+                               const uint32_t imageHeight,
+                               const std::vector<OdResults>& results)
+    {
+        for (const auto& result : results) {
+            DrawBox(image, imageWidth, imageHeight, result);
+            printf("Detection :: [%d" ", %d"
+                   ", %d" ", %d" "]\n",
+                   result.m_x0,
+                   result.m_y0,
+                   result.m_w,
+                   result.m_h);
+        }
+    }
+
+    /* Object detection VSI inference handler. */
+    bool ObjectDetectionHandlerVsi(ApplicationContext& ctx)
+    {
+        /* Image buffer. */
+        static uint8_t ImageBuf[IMAGE_DATA_SIZE];
+        static uint8_t ImageOut[IMAGE_DATA_SIZE];
+
+        /* Model object creation and initialisation. */
+        auto& model = ctx.Get<Model&>("model");
+
+        TfLiteTensor* inputTensor   = model.GetInputTensor(0);
+        TfLiteTensor* outputTensor0 = model.GetOutputTensor(0);
+        TfLiteTensor* outputTensor1 = model.GetOutputTensor(1);
+
+        if (!inputTensor->dims) {
+            printf_err("Invalid input tensor dims\n");
+            return false;
+        } else if (inputTensor->dims->size < 3) {
+            printf_err("Input tensor dimension should be >= 3\n");
+            return false;
+        }
+
+        TfLiteIntArray* inputShape = model.GetInputShape(0);
+        const int inputImgCols = inputShape->data[arm::app::YoloFastestModel::ms_inputColsIdx];
+        const int inputImgRows = inputShape->data[arm::app::YoloFastestModel::ms_inputRowsIdx];
+
+        /* Set up pre- and post-processing. */
+        arm::app::DetectorPreProcess preProcess =
+            arm::app::DetectorPreProcess(inputTensor, true, model.IsDataSigned());
+
+        std::vector<arm::app::OdResults> results;
+        const arm::app::object_detection::PostProcessParams postProcessParams{
+            inputImgRows,
+            inputImgCols,
+            arm::app::object_detection::originalImageSize,
+            arm::app::object_detection::anchor1,
+            arm::app::object_detection::anchor2};
+        arm::app::DetectorPostProcess postProcess =
+            arm::app::DetectorPostProcess(outputTensor0, outputTensor1, results, postProcessParams);
+
+        const size_t imgSz = inputTensor->bytes < IMAGE_DATA_SIZE ?
+                                                                  inputTensor->bytes : IMAGE_DATA_SIZE;
+
+        if (sizeof(ImageBuf) < imgSz) {
+            printf_err("Image buffer is insufficient\n");
+            return false;
+        }
+
+        /* Configure Input Video. */
+        if (VideoDrv_Configure(VIDEO_DRV_IN0,
+                               arm::app::object_detection::originalImageSize,
+                               arm::app::object_detection::originalImageSize,
+                               COLOR_RGB888, 24U) != VIDEO_DRV_OK) {
+            printf_err("Failed to configure video input\n");
+            return false;
+        }
+
+        /* Set Input Video buffer. */
+        if (VideoDrv_SetBuf(VIDEO_DRV_IN0,  ImageBuf, IMAGE_DATA_SIZE) != VIDEO_DRV_OK) {
+            printf_err("Failed to set buffer for video input\n");
+            return false;
+        }
+
+        /* Set Output Video file (only when using AVH - default: Display) */
+        //  if (VideoDrv_SetFile(VIDEO_DRV_OUT0, "output_image.png") != VIDEO_DRV_OK) {
+        //      printf_err("Failed to set filename for video output\n");
+        //      return 1;
+        //  }
+        /* Configure Output Video. */
+        if (VideoDrv_Configure(VIDEO_DRV_OUT0,
+                               arm::app::object_detection::originalImageSize,
+                               arm::app::object_detection::originalImageSize,
+                               COLOR_RGB888, 24U) != VIDEO_DRV_OK) {
+            printf_err("Failed to configure video output\n");
+            return false;
+        }
+
+        /* Set Output Video buffer. */
+        if (VideoDrv_SetBuf(VIDEO_DRV_OUT0, ImageOut, IMAGE_DATA_SIZE) != VIDEO_DRV_OK) {
+            printf_err("Failed to set buffer for video output\n");
+            return false;
+        }
+
+        auto imgCount = ctx.Get<uint32_t>("imgIndex");
+        void* imgFrame = nullptr;
+        void* outFrame = nullptr;
+
+        while (true) {
+#if VSI_IMAGE_INPUT
+            if (VideoDrv_SetFile(VIDEO_DRV_IN0, GetFilePath(imgCount)) != VIDEO_DRV_OK) {
+                printf_err("Failed to set filename for video input\n");
+                return false;
+            }
+#endif
+
+            VideoDrv_Status_t status;
+
+            results.clear();
+
+            /* Start video capture (single frame). */
+            if (VideoDrv_StreamStart(VIDEO_DRV_IN0, VIDEO_DRV_MODE_SINGLE) != VIDEO_DRV_OK) {
+                printf_err("Failed to start video capture\n");
+                return false;
+            }
+
+            /* Wait for video input frame. */
+            do {
+                status = VideoDrv_GetStatus(VIDEO_DRV_IN0);
+            } while (status.buf_empty != 0U);
+
+            /* Get input video frame buffer. */
+            imgFrame = VideoDrv_GetFrameBuf(VIDEO_DRV_IN0);
+
+            /* Run the pre-processing, inference and post-processing. */
+            if (!preProcess.DoPreProcess(imgFrame, imgSz)) {
+                printf_err("Pre-processing failed.\n");
+                return false;
+            }
+
+            /* Run inference over this image. */
+            printf("\rImage %" PRIu32 "; ", ++imgCount);
+
+            if (!model.RunInference()) {
+                printf_err("Inference failed.\n");
+                return false;
+            }
+
+            if (!postProcess.DoPostProcess()) {
+                printf_err("Post-processing failed.\n");
+                return false;
+            }
+
+            /* Release input frame. */
+            VideoDrv_ReleaseFrame(VIDEO_DRV_IN0);
+
+            arm::app::DrawDetectionBoxesVsi(static_cast<uint8_t*>(imgFrame), inputImgCols, inputImgRows, results);
+
+            /* Get output video frame buffer. */
+            outFrame = VideoDrv_GetFrameBuf(VIDEO_DRV_OUT0);
+
+            /* Copy image frame with detection boxes to output frame buffer. */
+            memcpy(outFrame, imgFrame, IMAGE_DATA_SIZE);
+
+            /* Release output frame. */
+            VideoDrv_ReleaseFrame(VIDEO_DRV_OUT0);
+
+            /* Start video output (single frame). */
+            VideoDrv_StreamStart(VIDEO_DRV_OUT0, VIDEO_DRV_MODE_SINGLE);
+
+            /* Check for end of stream (when using AVH with file as Video input). */
+            if (status.eos != 0U) {
+                while (VideoDrv_GetStatus(VIDEO_DRV_OUT0).buf_empty == 0U);
+                break;
+            }
+        }
+
+        IncrementAppCtxIfmIdx(ctx, "imgIndex");
+
+        /* De-initialize Video Interface. */
+        //VideoDrv_Uninitialize();
+        return true;
+    }
+
+#endif
+
     /**
      * @brief           Presents inference results along using the data presentation
      *                  object.
diff --git a/source/use_case/object_detection/usecase.cmake b/source/use_case/object_detection/usecase.cmake
index a36d3e0..cb0236b 100644
--- a/source/use_case/object_detection/usecase.cmake
+++ b/source/use_case/object_detection/usecase.cmake
@@ -1,5 +1,5 @@
 #----------------------------------------------------------------------------
-#  SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
+#  SPDX-FileCopyrightText: Copyright 2022, 2024 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");
@@ -36,10 +36,24 @@
     BOOL)
 
 # Generate input files
-generate_images_code("${${use_case}_FILE_PATH}"
-                     ${SRC_GEN_DIR}
-                     ${INC_GEN_DIR}
-                     "${${use_case}_IMAGE_SIZE}")
+if (VSI_IMAGE_INPUT)
+    set(${use_case}_COMPILE_DEFS "VSI_IMAGE_INPUT")
+
+    generate_images_code(
+            INPUT_DIR "${${use_case}_FILE_PATH}"
+            SRC_OUT ${SRC_GEN_DIR}
+            HDR_OUT ${INC_GEN_DIR}
+            IMG_SIZE "${${use_case}_IMAGE_SIZE}"
+            GENERATE_FILE_PATHS
+    )
+else ()
+    generate_images_code(
+            INPUT_DIR "${${use_case}_FILE_PATH}"
+            SRC_OUT ${SRC_GEN_DIR}
+            HDR_OUT ${INC_GEN_DIR}
+            IMG_SIZE "${${use_case}_IMAGE_SIZE}"
+    )
+endif ()
 
 USER_OPTION(${use_case}_ACTIVATION_BUF_SZ "Activation buffer size for the chosen model"
     0x00082000
diff --git a/source/use_case/vww/usecase.cmake b/source/use_case/vww/usecase.cmake
index 7a4d876..99580cc 100644
--- a/source/use_case/vww/usecase.cmake
+++ b/source/use_case/vww/usecase.cmake
@@ -1,5 +1,5 @@
 #----------------------------------------------------------------------------
-#  SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates <open-source-office@arm.com>
+#  SPDX-FileCopyrightText: Copyright 2021, 2024 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");
@@ -58,7 +58,9 @@
 )
 
 # Generate input files
-generate_images_code("${${use_case}_FILE_PATH}"
-                     ${SRC_GEN_DIR}
-                     ${INC_GEN_DIR}
-                     "${${use_case}_IMAGE_SIZE}")
+generate_images_code(
+    INPUT_DIR       "${${use_case}_FILE_PATH}"
+    SRC_OUT         ${SRC_GEN_DIR}
+    HDR_OUT         ${INC_GEN_DIR}
+    IMG_SIZE        "${${use_case}_IMAGE_SIZE}"
+)