MLBEDSW-3425: Added external API for driver actions

Added external API to add driver actions to a command stream.

Change-Id: Ie4779c1c745defc5769fa694358470cd6aea191c
Signed-off-by: Louis Verhaard <louis.verhaard@arm.com>
diff --git a/ethosu/vela/api.py b/ethosu/vela/api.py
index f64a38f..22f81d0 100644
--- a/ethosu/vela/api.py
+++ b/ethosu/vela/api.py
@@ -453,3 +453,19 @@
     from . import register_command_stream_generator
 
     return register_command_stream_generator.generate_register_command_stream(npu_op_list, accelerator)
+
+
+def npu_create_driver_payload(register_command_stream: List[int], accelerator: NpuAccelerator) -> bytes:
+    """
+    Public facing API for generating driver payload, containing a driver header
+    and the given Ethos-U register command stream.
+    Returns the payload, in little endian format, which must be placed in memory on a 16-byte aligned
+    address.
+
+    :param register_command_stream: List[int] register commands, as a list of 32-bit integers
+    :param accelerator: NpuAccelerator enum to pick the correct accelerator
+    :return driver payload, as a byte array
+    """
+    from . import driver_actions
+
+    return driver_actions.npu_create_driver_payload(register_command_stream, accelerator)
diff --git a/ethosu/vela/architecture_features.py b/ethosu/vela/architecture_features.py
index 18846cf..9f27b7e 100644
--- a/ethosu/vela/architecture_features.py
+++ b/ethosu/vela/architecture_features.py
@@ -605,7 +605,6 @@
         return mem_port_mapping[mem_port]
 
     def _set_default_sys_config(self):
-        print(f"Warning: Using {ArchitectureFeatures.DEFAULT_CONFIG} values for system configuration")
         # ArchitectureFeatures.DEFAULT_CONFIG values
         if self.is_ethos_u65_system:
             # Default Ethos-U65 system configuration
@@ -625,7 +624,6 @@
             self.memory_clock_scales[MemArea.OffChipFlash] = 0.125  # 1 / 8
 
     def _set_default_mem_mode(self):
-        print(f"Warning: Using {ArchitectureFeatures.DEFAULT_CONFIG} values for memory mode")
         # ArchitectureFeatures.DEFAULT_CONFIG values
         if self.is_ethos_u65_system:
             # Default Ethos-U65 memory mode
@@ -815,3 +813,18 @@
             result = self.vela_config.get(section, key)
 
         return result
+
+
+def create_default_arch(accelerator: Accelerator) -> ArchitectureFeatures:
+    """Creates architecture features object using default settings"""
+    return ArchitectureFeatures(
+        vela_config_files=None,
+        accelerator_config=accelerator.value,
+        system_config=ArchitectureFeatures.DEFAULT_CONFIG,
+        memory_mode=ArchitectureFeatures.DEFAULT_CONFIG,
+        override_block_config=None,
+        block_config_limit=None,
+        max_blockdep=ArchitectureFeatures.MAX_BLOCKDEP,
+        weight_estimation_scaling=1.0,
+        verbose_config=False,
+    )
diff --git a/ethosu/vela/driver_actions.py b/ethosu/vela/driver_actions.py
index 29c2b18..86bed11 100644
--- a/ethosu/vela/driver_actions.py
+++ b/ethosu/vela/driver_actions.py
@@ -15,10 +15,15 @@
 # limitations under the License.
 # Description:
 # Creates driver actions that are embedded in the custom operator payload.
+import struct
 from typing import List
 
 import numpy as np
 
+from .api import NpuAccelerator
+from .architecture_features import Accelerator
+from .architecture_features import ArchitectureFeatures
+from .architecture_features import create_default_arch
 from .ethos_u55_regs.ethos_u55_regs import ARCH_VER
 from .ethos_u55_regs.ethos_u55_regs import config_r
 from .ethos_u55_regs.ethos_u55_regs import id_r
@@ -106,3 +111,24 @@
 def emit_dump_shram(data: List[int]):
     assert data is not None
     data.append(make_da_tag(DACommands.DumpSHRAM, 0, 0))
+
+
+def create_driver_payload(register_command_stream: List[int], arch: ArchitectureFeatures) -> bytes:
+    """Creates driver header and includes the given command
+    """
+    # Prepare driver actions for this command tensor
+    da_list = []
+    emit_fourcc(da_list, "COP1")
+    emit_config(da_list, 0, 1, arch)
+    emit_cmd_stream_header(da_list, len(register_command_stream))
+
+    # Append command stream words
+    da_list.extend(register_command_stream)
+    # Convert to bytes, in little endian format
+    return struct.pack("<{0}I".format(len(da_list)), *da_list)
+
+
+def npu_create_driver_payload(register_command_stream: List[int], accelerator: NpuAccelerator) -> bytes:
+    """Internal implementation of the public facing API to create driver payload"""
+    arch = create_default_arch(Accelerator.from_npu_accelerator(accelerator))
+    return create_driver_payload(register_command_stream, arch)
diff --git a/ethosu/vela/npu_serialisation.py b/ethosu/vela/npu_serialisation.py
index 04534cc..a11907b 100644
--- a/ethosu/vela/npu_serialisation.py
+++ b/ethosu/vela/npu_serialisation.py
@@ -15,8 +15,6 @@
 # limitations under the License.
 # Description:
 # Serialises and packs an NPU subgraph into tensors.
-import struct
-
 import numpy as np
 
 from . import driver_actions
@@ -70,17 +68,7 @@
     flash_size = sg.memory_used.get(flash_area, 0)
     scratch_size = sg.memory_used.get(scratch_area, 0)
 
-    # Prepare driver actions for this command tensor
-    da_list = []
-    driver_actions.emit_fourcc(da_list, "COP1")
-    driver_actions.emit_config(da_list, 0, 1, arch)
-    driver_actions.emit_cmd_stream_header(da_list, len(sg.register_command_stream))
-
-    # Append command stream words
-    da_list.extend(sg.register_command_stream)
-
-    # Convert to bytes
-    payload_bytes = struct.pack("<{0}I".format(len(da_list)), *da_list)
+    payload_bytes = driver_actions.create_driver_payload(sg.register_command_stream, arch)
 
     command_stream_size_bytes = len(payload_bytes)
 
diff --git a/ethosu/vela/register_command_stream_generator.py b/ethosu/vela/register_command_stream_generator.py
index 04f7072..9d79d58 100644
--- a/ethosu/vela/register_command_stream_generator.py
+++ b/ethosu/vela/register_command_stream_generator.py
@@ -55,6 +55,7 @@
 from .architecture_features import Accelerator
 from .architecture_features import ArchitectureFeatures
 from .architecture_features import Block
+from .architecture_features import create_default_arch
 from .architecture_features import Rect
 from .architecture_features import SharedBufferArea
 from .architecture_features import SHRAMElements
@@ -1282,16 +1283,6 @@
     """
     accelerator = Accelerator.from_npu_accelerator(npu_accelerator)
     emit = CommandStreamEmitter()
-    arch = ArchitectureFeatures(
-        vela_config_files=None,
-        accelerator_config=accelerator.value,
-        system_config=ArchitectureFeatures.DEFAULT_CONFIG,
-        memory_mode=ArchitectureFeatures.DEFAULT_CONFIG,
-        override_block_config=None,
-        block_config_limit=None,
-        max_blockdep=ArchitectureFeatures.MAX_BLOCKDEP,
-        weight_estimation_scaling=1.0,
-        verbose_config=False,
-    )
+    arch = create_default_arch(accelerator)
     generate_command_stream(emit, npu_op_list, arch)
     return emit.to_list()
diff --git a/ethosu/vela/test/extapi/test_extapi_create_payload.py b/ethosu/vela/test/extapi/test_extapi_create_payload.py
new file mode 100644
index 0000000..06dd220
--- /dev/null
+++ b/ethosu/vela/test/extapi/test_extapi_create_payload.py
@@ -0,0 +1,42 @@
+# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the License); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an AS IS BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Description:
+# Contains unit tests for npu_create_driver_payload API for an external consumer
+import random
+
+import pytest
+
+from ethosu.vela.api import npu_create_driver_payload
+from ethosu.vela.api import NpuAccelerator
+
+
+@pytest.mark.parametrize("accelerator", list(NpuAccelerator))
+def test_create_driver_payload(accelerator: NpuAccelerator):
+    """Tests npu_create_driver_payload"""
+    # Generate a random command stream with defined beginning and end
+    random.seed(0)
+    num_commands = 793
+    register_command_stream = random.choices(range(1 << 32), k=num_commands)
+    register_command_stream[0] = 0xFEDCBA98
+    register_command_stream[-1] = 0xA0B1C2D3
+    payload = npu_create_driver_payload(register_command_stream, accelerator)
+    header_size = 32  # expected driver header size in bytes
+    assert len(payload) == header_size + 4 * num_commands
+    # Check that the first register command is located directly after the header
+    assert list(payload[header_size : header_size + 4]) == [0x98, 0xBA, 0xDC, 0xFE]
+    # Check that the last register command is present in the payload
+    assert list(payload[-4:]) == [0xD3, 0xC2, 0xB1, 0xA0]
diff --git a/ethosu/vela/test/testutil.py b/ethosu/vela/test/testutil.py
index 7cdd4f5..ee407b6 100644
--- a/ethosu/vela/test/testutil.py
+++ b/ethosu/vela/test/testutil.py
@@ -27,17 +27,7 @@
 
 
 def create_arch():
-    return architecture_features.ArchitectureFeatures(
-        vela_config_files=None,
-        accelerator_config=architecture_features.Accelerator.Ethos_U55_128.value,
-        system_config=architecture_features.ArchitectureFeatures.DEFAULT_CONFIG,
-        memory_mode=architecture_features.ArchitectureFeatures.DEFAULT_CONFIG,
-        override_block_config=None,
-        block_config_limit=None,
-        max_blockdep=0,
-        weight_estimation_scaling=1.0,
-        verbose_config=False,
-    )
+    return architecture_features.create_default_arch(architecture_features.Accelerator.Ethos_U55_128)
 
 
 def default_quant_params():
diff --git a/ethosu/vela/vela.py b/ethosu/vela/vela.py
index 6fc5c47..7b76685 100644
--- a/ethosu/vela/vela.py
+++ b/ethosu/vela/vela.py
@@ -41,6 +41,7 @@
 from .tensor import Tensor
 from .tflite_mapping import builtin_operator_map
 from .tflite_mapping import builtin_type_name
+from ethosu.vela.architecture_features import ArchitectureFeatures
 
 
 def process(input_name, enable_debug_db, arch, model_reader_options, compiler_options, scheduler_options):
@@ -372,6 +373,12 @@
             "".format(args.cpu_tensor_alignment)
         )
 
+    if args.system_config == ArchitectureFeatures.DEFAULT_CONFIG:
+        print(f"Warning: Using {ArchitectureFeatures.DEFAULT_CONFIG} values for system configuration")
+
+    if args.memory_mode == ArchitectureFeatures.DEFAULT_CONFIG:
+        print(f"Warning: Using {ArchitectureFeatures.DEFAULT_CONFIG} values for memory mode")
+
     arch = architecture_features.ArchitectureFeatures(
         vela_config_files=args.config,
         system_config=args.system_config,