IVGCVSW-7342 Add Slice support to TOSA Reference Backend

Signed-off-by: Cathal Corbett <cathal.corbett@arm.com>
Change-Id: I8be286b69bebd4cd36033e3145632bb043938d16
diff --git a/src/backends/backendsCommon/test/CMakeLists.txt b/src/backends/backendsCommon/test/CMakeLists.txt
index 881e4d6..5fcc8b5 100644
--- a/src/backends/backendsCommon/test/CMakeLists.txt
+++ b/src/backends/backendsCommon/test/CMakeLists.txt
@@ -52,6 +52,7 @@
     ReshapeEndToEndTestImpl.hpp
     ResizeEndToEndTestImpl.hpp
     RuntimeTestImpl.hpp
+    SliceEndToEndTestImpl.hpp
     SpaceToDepthEndToEndTestImpl.cpp
     SpaceToDepthEndToEndTestImpl.hpp
     SplitterEndToEndTestImpl.hpp
diff --git a/src/backends/backendsCommon/test/SliceEndToEndTestImpl.hpp b/src/backends/backendsCommon/test/SliceEndToEndTestImpl.hpp
new file mode 100644
index 0000000..811ce27
--- /dev/null
+++ b/src/backends/backendsCommon/test/SliceEndToEndTestImpl.hpp
@@ -0,0 +1,99 @@
+//
+// Copyright © 2022 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+#pragma once
+
+#include <armnn/INetwork.hpp>
+
+#include <CommonTestUtils.hpp>
+#include <ResolveType.hpp>
+
+#include <doctest/doctest.h>
+
+namespace
+{
+
+template<typename armnn::DataType DataType>
+armnn::INetworkPtr CreateSliceNetwork(const armnn::TensorShape& inputShape,
+                                      const armnn::TensorShape& outputShape,
+                                      const armnn::SliceDescriptor& descriptor,
+                                      const float qScale = 1.0f,
+                                      const int32_t qOffset = 0)
+{
+    using namespace armnn;
+
+    INetworkPtr network(INetwork::Create());
+
+    TensorInfo inputTensorInfo(inputShape, DataType, qScale, qOffset, true);
+    TensorInfo outputTensorInfo(outputShape, DataType, qScale, qOffset);
+
+
+    IConnectableLayer* slice  = network->AddSliceLayer(descriptor, "slice");
+    IConnectableLayer* input  = network->AddInputLayer(0, "input");
+    IConnectableLayer* output = network->AddOutputLayer(0, "output");
+
+    Connect(input, slice, inputTensorInfo, 0, 0);
+    Connect(slice, output, outputTensorInfo, 0, 0);
+
+    return network;
+}
+
+template<armnn::DataType ArmnnType, typename T = armnn::ResolveType<ArmnnType>>
+void SliceEndToEnd(const std::vector<armnn::BackendId>& backends)
+{
+    using namespace armnn;
+
+    const TensorShape& inputShape  = { 3, 2, 3 };
+    const TensorShape& outputShape = { 2, 1, 3 };
+
+    SliceDescriptor descriptor;
+    descriptor.m_Begin = { 1, 0, 0 };
+    descriptor.m_Size  = { 2, 1, 3 };
+
+    INetworkPtr network = CreateSliceNetwork<ArmnnType>(inputShape, outputShape, descriptor);
+
+    CHECK(network);
+
+    std::vector<T> inputData{ 1, 1, 1, 2, 2, 2,
+                              3, 3, 3, 4, 4, 4,
+                              5, 5, 5, 6, 6, 6 };
+    std::vector<T> expectedOutput{ 3, 3, 3,
+                                   5, 5, 5 };
+
+    std::map<int, std::vector<T>> inputTensorData = { { 0, inputData } };
+    std::map<int, std::vector<T>> expectedOutputData = { { 0, expectedOutput } };
+
+    EndToEndLayerTestImpl<ArmnnType, ArmnnType>(std::move(network), inputTensorData, expectedOutputData, backends);
+}
+
+template<armnn::DataType ArmnnType>
+void SliceEndToEndFloat16(const std::vector<armnn::BackendId>& backends)
+{
+    using namespace armnn;
+    using namespace half_float::literal;
+    using Half = half_float::half;
+
+    const TensorShape& inputShape = { 3, 2, 3 };
+    const TensorShape& outputShape = { 2, 1, 3 };
+
+    SliceDescriptor descriptor;
+    descriptor.m_Begin = { 1, 0, 0 };
+    descriptor.m_Size  = { 2, 1, 3 };
+
+    INetworkPtr network = CreateSliceNetwork<ArmnnType>(inputShape, outputShape, descriptor);
+    CHECK(network);
+
+    std::vector<Half> inputData{ 1._h, 1._h, 1._h, 2._h, 2._h, 2._h,
+                                 3._h, 3._h, 3._h, 4._h, 4._h, 4._h,
+                                 5._h, 5._h, 5._h, 6._h, 6._h, 6._h };
+    std::vector<Half> expectedOutput{ 3._h, 3._h, 3._h,
+                                      5._h, 5._h, 5._h };
+
+    std::map<int, std::vector<Half>> inputTensorData = { { 0, inputData } };
+    std::map<int, std::vector<Half>> expectedOutputData = { { 0, expectedOutput } };
+
+    EndToEndLayerTestImpl<ArmnnType, ArmnnType>(std::move(network), inputTensorData, expectedOutputData, backends);
+}
+
+} // anonymous namespace
\ No newline at end of file
diff --git a/src/backends/tosaCommon/TosaMappings.cpp b/src/backends/tosaCommon/TosaMappings.cpp
index 318735d..15629ff 100644
--- a/src/backends/tosaCommon/TosaMappings.cpp
+++ b/src/backends/tosaCommon/TosaMappings.cpp
@@ -62,6 +62,11 @@
             auto reshapeDesc = PolymorphicDowncast<const ReshapeDescriptor*>(&descriptor);
             return ConvertReshapeToTosaOperator(layer, inputs, outputs, reshapeDesc);
         }
+        case LayerType::Slice:
+        {
+            auto sliceDesc = PolymorphicDowncast<const SliceDescriptor*>(&descriptor);
+            return ConvertSliceToTosaOperator(layer, inputs, outputs, sliceDesc);
+        }
         default:
         {
             return CreateEmptyTosaSerializationBasicBlock();
diff --git a/src/backends/tosaCommon/operatorMappings/CMakeLists.txt b/src/backends/tosaCommon/operatorMappings/CMakeLists.txt
index 7733d01..cb1d68e 100644
--- a/src/backends/tosaCommon/operatorMappings/CMakeLists.txt
+++ b/src/backends/tosaCommon/operatorMappings/CMakeLists.txt
@@ -16,6 +16,8 @@
         Pooling2DOperator.cpp
         ReshapeOperator.hpp
         ReshapeOperator.cpp
+        SliceOperator.hpp
+        SliceOperator.cpp
         TosaOperatorUtils.hpp
     )
 
diff --git a/src/backends/tosaCommon/operatorMappings/SliceOperator.cpp b/src/backends/tosaCommon/operatorMappings/SliceOperator.cpp
new file mode 100644
index 0000000..fc2e40a
--- /dev/null
+++ b/src/backends/tosaCommon/operatorMappings/SliceOperator.cpp
@@ -0,0 +1,57 @@
+//
+// Copyright © 2022 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "SliceOperator.hpp"
+
+TosaSerializationBasicBlock* ConvertSliceToTosaOperator(const Layer* layer,
+                                                        const std::vector<const TensorInfo*>& inputs,
+                                                        const std::vector<const TensorInfo*>& outputs,
+                                                        const SliceDescriptor* sliceDescriptor)
+{
+    std::string inputName = std::string("input0_");
+    std::string outputName = std::string("output0_");
+    std::string blockName  = std::string("Op_SLICE_block_") + GetUniqueTosaMappingID();
+
+    // If a layer is present then the block will be used for execution, so input and output names need to be determined
+    // using the previous and following layers so the graph is connected correctly. For validation this doesn't matter.
+    if(layer != nullptr)
+    {
+        // Get the layers connected to the input slots and determine unique layer names.
+        Layer& connectedLayer = layer->GetInputSlot(0).GetConnectedOutputSlot()->GetOwningLayer();
+        inputName = GenerateUniqueName(connectedLayer, 0);
+
+        // Get the layer connected to the output slot and determine unique layer name.
+        Layer& connectedOutputLayer = layer->GetOutputSlot().GetConnection(0)->GetOwningLayer();
+        outputName = GenerateUniqueName(connectedOutputLayer, 0);
+    }
+
+    std::vector<int32_t> begin(sliceDescriptor->m_Begin.begin(), sliceDescriptor->m_Begin.end());
+    std::vector<int32_t> size(sliceDescriptor->m_Size.begin(), sliceDescriptor->m_Size.end());
+
+    TosaSliceAttribute attribute(begin, size);
+
+    auto* op = new TosaSerializationOperator(Op_SLICE,
+                                             Attribute_SliceAttribute,
+                                             &attribute,
+                                             {inputName},
+                                             {outputName});
+
+    std::vector<int32_t> inputShape = GetTosaTensorShape(inputs[0]->GetShape());
+    DType inputDType = ArmNNToDType(inputs[0]->GetDataType());
+
+    std::vector<int32_t> outputShape = GetTosaTensorShape(outputs[0]->GetShape());
+    DType outputDType = ArmNNToDType(outputs[0]->GetDataType());
+
+    auto* inputTensor  = new TosaSerializationTensor(inputName, inputShape, inputDType, {});
+    auto* outputTensor = new TosaSerializationTensor(outputName, outputShape, outputDType, {});
+
+    // operatorInputNames/operatorOutputNames ends up being the same as
+    // blockInputNames/blockOutputNames for one-to-one ArmNN to TOSA mappings
+    return new TosaSerializationBasicBlock(blockName, // name
+                                           {op}, // operators
+                                           {inputTensor, outputTensor}, // tensors
+                                           {inputName}, // inputs
+                                           {outputName}); // outputs
+}
\ No newline at end of file
diff --git a/src/backends/tosaCommon/operatorMappings/SliceOperator.hpp b/src/backends/tosaCommon/operatorMappings/SliceOperator.hpp
new file mode 100644
index 0000000..127cc81
--- /dev/null
+++ b/src/backends/tosaCommon/operatorMappings/SliceOperator.hpp
@@ -0,0 +1,20 @@
+//
+// Copyright © 2022 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include "TosaOperatorUtils.hpp"
+
+#include <Layer.hpp>
+
+#include <tosa_serialization_handler.h>
+
+using namespace armnn;
+using namespace tosa;
+
+TosaSerializationBasicBlock* ConvertSliceToTosaOperator(const Layer* layer,
+                                                        const std::vector<const TensorInfo*>& inputs,
+                                                        const std::vector<const TensorInfo*>& outputs,
+                                                        const SliceDescriptor* sliceDescriptor);
\ No newline at end of file
diff --git a/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp b/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp
index 0711095..a3597f0 100644
--- a/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp
+++ b/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp
@@ -10,4 +10,5 @@
 #include "Conv2dOperator.hpp"
 #include "AvgPool2DIgnoreValueOperator.hpp"
 #include "Pooling2DOperator.hpp"
-#include "ReshapeOperator.hpp"
\ No newline at end of file
+#include "ReshapeOperator.hpp"
+#include "SliceOperator.hpp"
\ No newline at end of file
diff --git a/src/backends/tosaCommon/test/OneToOneMappingTests.cpp b/src/backends/tosaCommon/test/OneToOneMappingTests.cpp
index b1fa684..07ffae4 100644
--- a/src/backends/tosaCommon/test/OneToOneMappingTests.cpp
+++ b/src/backends/tosaCommon/test/OneToOneMappingTests.cpp
@@ -375,6 +375,69 @@
                                         LayerType::Reshape);
 }
 
+TEST_CASE("GetTosaMapping_SliceLayer")
+{
+    TensorInfo inputInfo = TensorInfo({ 3, 2, 3 }, DataType::Float32);
+    TensorInfo outputInfo = TensorInfo({ 2, 1, 3 }, DataType::Float32);
+
+    std::vector<std::vector<int32_t>> inputShape  = {{ 3, 2, 3 }};
+    std::vector<std::vector<int32_t>> outputShape = {{ 2, 1, 3 }};
+
+    SliceDescriptor descriptor;
+    descriptor.m_Begin = { 3 };
+    descriptor.m_Size  = { 3 };
+
+    TosaSerializationBasicBlock* basicBlock =
+        GetTosaMapping(nullptr, LayerType::Slice, {&inputInfo}, {&outputInfo}, descriptor);
+    AssertTosaOneToOneMappingBasicBlock(basicBlock,
+                                        inputShape,
+                                        outputShape,
+                                        Op_SLICE,
+                                        Attribute_SliceAttribute,
+                                        descriptor,
+                                        LayerType::Slice);
+}
+
+TEST_CASE("GetTosaMappingFromLayer_SliceLayer")
+{
+    IRuntime::CreationOptions options;
+    IRuntimePtr runtime(IRuntime::Create(options));
+
+    // Builds up the structure of the network.
+    INetworkPtr net(INetwork::Create());
+
+    TensorInfo inputInfo = TensorInfo({ 3, 2, 3 }, DataType::Float32);
+    TensorInfo outputInfo = TensorInfo({ 2, 1, 3 }, DataType::Float32);
+
+    std::vector<std::vector<int32_t>> inputShape  = {{ 3, 2, 3 }};
+    std::vector<std::vector<int32_t>> outputShape = {{ 2, 1, 3 }};
+
+    SliceDescriptor descriptor;
+    descriptor.m_Begin = { 1, 0, 0 };
+    descriptor.m_Size  = { 2, 1, 3 };
+
+    IConnectableLayer* input  = net->AddInputLayer(0, "input");
+    IConnectableLayer* slice  = net->AddSliceLayer(descriptor, "slice");
+    IConnectableLayer* output = net->AddOutputLayer(0, "output");
+
+    input->GetOutputSlot(0).Connect(slice->GetInputSlot(0));
+    slice->GetOutputSlot(0).Connect(output->GetInputSlot(0));
+
+    input->GetOutputSlot(0).SetTensorInfo(inputInfo);
+    slice->GetOutputSlot(0).SetTensorInfo(outputInfo);
+
+    TosaSerializationBasicBlock* basicBlock =
+        GetTosaMappingFromLayer(PolymorphicDowncast<Layer*>(slice));
+    AssertTosaOneToOneMappingBasicBlock(basicBlock,
+                                        inputShape,
+                                        outputShape,
+                                        Op_SLICE,
+                                        Attribute_SliceAttribute,
+                                        descriptor,
+                                        LayerType::Slice);
+}
+
+
 TEST_CASE("GetTosaMapping_Unimplemented")
 {
     TosaSerializationBasicBlock* basicBlock =
diff --git a/src/backends/tosaCommon/test/TosaTestUtils.hpp b/src/backends/tosaCommon/test/TosaTestUtils.hpp
index 5c10a6d..93b9e7d 100644
--- a/src/backends/tosaCommon/test/TosaTestUtils.hpp
+++ b/src/backends/tosaCommon/test/TosaTestUtils.hpp
@@ -122,6 +122,27 @@
                                                              1,
                                                              std::multiplies<int32_t>());
             CHECK(numInputElements == numAttributeShapeElements);
+
+            break;
+        }
+        case LayerType::Slice:
+        {
+            auto sliceDesc = PolymorphicDowncast<const SliceDescriptor*>(&descriptor);
+            TosaSliceAttribute reshapeAttribute(attribute);
+
+            std::vector<int32_t> begin(sliceDesc->m_Begin.begin(), sliceDesc->m_Begin.end());
+            std::vector<int32_t> size(sliceDesc->m_Size.begin(), sliceDesc->m_Size.end());
+
+            CHECK(begin == reshapeAttribute.start());
+            CHECK(size == reshapeAttribute.size());
+
+            CHECK(begin.size() == inputShape.size());
+            CHECK(size.size() == inputShape.size());
+
+            CHECK(begin.size() == outputShape.size());
+            CHECK(size.size() == outputShape.size());
+
+            break;
         }
         default:
             break;
diff --git a/src/backends/tosaReference/TosaRefLayerSupport.cpp b/src/backends/tosaReference/TosaRefLayerSupport.cpp
index 5cda85a..daa27f6 100644
--- a/src/backends/tosaReference/TosaRefLayerSupport.cpp
+++ b/src/backends/tosaReference/TosaRefLayerSupport.cpp
@@ -303,6 +303,24 @@
             return RunTosaLayerChecksSingleDataType(
                 op, inputs, outputs, supportedAttributes, supportedTypes, reasonIfUnsupported);
         }
+        case tosa::Op_SLICE:
+        {
+            std::vector<Attribute> supportedAttributes = { Attribute_SliceAttribute };
+
+            std::vector<DType> supportedTypes =
+            {
+                DType_FP16,
+                DType_FP32,
+                DType_INT8,
+                DType_INT16,
+                DType_INT32,
+                DType_BOOL
+            };
+
+            // Check the attribute, data types and bounds for inputs and outputs.
+            return RunTosaLayerChecksSingleDataType(
+                op, inputs, outputs, supportedAttributes, supportedTypes, reasonIfUnsupported);
+        }
         default:
             SetValueChecked(reasonIfUnsupported, "Operation is currently unsupported by the TOSA Reference Backend.");
             return false;
@@ -351,6 +369,7 @@
         }
         case LayerType::Pooling2d:
         case LayerType::Reshape:
+        case LayerType::Slice:
             // Setup inputs and outputs
             inputInfos.push_back(&infos[0]);
             outputInfos.push_back(&infos[1]);
diff --git a/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp b/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp
index aaf8a67..2f12310 100644
--- a/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp
+++ b/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp
@@ -9,6 +9,7 @@
 #include "backendsCommon/test/Convolution2dEndToEndTestImpl.hpp"
 #include "backendsCommon/test/Pooling2dEndToEndTestImpl.hpp"
 #include "backendsCommon/test/ReshapeEndToEndTestImpl.hpp"
+#include "backendsCommon/test/SliceEndToEndTestImpl.hpp"
 
 #include <doctest/doctest.h>
 
@@ -91,4 +92,20 @@
     ReshapeEndToEndFloat16<DataType::Float16>(tosaDefaultBackends);
 }
 
+// Slice
+TEST_CASE("TosaRefSliceEndtoEndTestFloat32")
+{
+    SliceEndToEnd<DataType::Float32>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSliceEndtoEndTestInt32")
+{
+    SliceEndToEnd<DataType::Signed32>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSliceEndtoEndTestFloat16")
+{
+    SliceEndToEndFloat16<DataType::Float16>(tosaDefaultBackends);
+}
+
 }
\ No newline at end of file
diff --git a/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp b/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp
index 86b01d8..a1bab83 100644
--- a/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp
+++ b/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp
@@ -329,4 +329,56 @@
         "has an unsupported data type: DType_UNKNOWN") != std::string::npos);
 }
 
+TEST_CASE("IsLayerSupportedTosaReferenceSlice")
+{
+    TensorShape inShape = {3,2,3};
+    TensorShape outShape = {2,1,3};
+    TensorInfo in(inShape, DataType::Float32);
+    TensorInfo out(outShape, DataType::Float32);
+
+    SliceDescriptor descriptor;
+    descriptor.m_Begin = {1,0,0 };
+    descriptor.m_Size  = {2,1,3 };
+
+    TosaRefLayerSupport supportChecker;
+    std::string reasonIfNotSupported;
+    auto supported = supportChecker.IsLayerSupported(LayerType::Slice,
+                                                     {in, out},
+                                                     descriptor,
+                                                     EmptyOptional(),
+                                                     EmptyOptional(),
+                                                     reasonIfNotSupported);
+
+    CHECK(supported);
+}
+
+TEST_CASE("IsLayerSupportedTosaReferenceSliceUnsupported")
+{
+    TensorShape inShape = {3,2,3};
+    TensorShape outShape = {2,1,3};
+    TensorInfo in(inShape, DataType::Signed64);
+    TensorInfo out(outShape, DataType::Signed64);
+
+    SliceDescriptor descriptor;
+    descriptor.m_Begin = {1,0,0};
+    descriptor.m_Size  = {2,1,3};
+
+    TosaRefLayerSupport supportChecker;
+    std::string reasonIfNotSupported;
+    auto supported = supportChecker.IsLayerSupported(LayerType::Slice,
+                                                     {in, out},
+                                                     descriptor,
+                                                     EmptyOptional(),
+                                                     EmptyOptional(),
+                                                     reasonIfNotSupported);
+
+    CHECK(!supported);
+    REQUIRE(reasonIfNotSupported.find(
+        "TOSA Reference Operator: Op_SLICE for input: input0_") != std::string::npos);
+    REQUIRE(reasonIfNotSupported.find(
+        "TOSA Reference Operator: Op_SLICE for output: output0_") != std::string::npos);
+    REQUIRE(reasonIfNotSupported.find(
+        "has an unsupported data type: DType_UNKNOWN") != std::string::npos);
+}
+
 }