Add Split support to TOSA Reference Backend

* Resolves IVGCVSW-7918

Signed-off-by: Kevin May <kevin.may@arm.com>
Change-Id: Ic2afaa55f7ee88ce4c9b8ea696eef5f28663f8c6
diff --git a/src/backends/backendsCommon/test/SplitterEndToEndTestImpl.hpp b/src/backends/backendsCommon/test/SplitterEndToEndTestImpl.hpp
index da4c6a6..5a4990f 100644
--- a/src/backends/backendsCommon/test/SplitterEndToEndTestImpl.hpp
+++ b/src/backends/backendsCommon/test/SplitterEndToEndTestImpl.hpp
@@ -48,6 +48,7 @@
     splitterDimSizes[splitAxis] /= numSplit;
 
     SplitterDescriptor splitDesc(numSplit, inputShape.GetNumDimensions());
+    splitDesc.SetAxis(static_cast<int32_t>(splitAxis));
     for (unsigned int g = 0; g < numSplit; ++g)
     {
         // Set the size of the views.
@@ -101,6 +102,35 @@
 }
 
 template<armnn::DataType ArmnnType>
+void Splitter1dEndToEndFloat16(const std::vector<BackendId>& backends)
+{
+    using namespace armnn;
+    using namespace half_float::literal;
+    using Half = half_float::half;
+
+    unsigned int splitAxis = 0;
+    unsigned int numSplit = 2;
+    const TensorShape& inputShape = { 4 };
+    const std::vector<TensorShape> outputShapes{{ 2 }, { 2 }};
+
+    // Builds up the structure of the network
+    INetworkPtr net = CreateSplitterNetwork<ArmnnType>(inputShape, outputShapes, splitAxis, numSplit);
+
+    CHECK(net);
+
+    // Creates structures for input & output.
+    std::vector<Half> inputData{ 1._h, 2._h, 3._h, 4._h };
+
+    std::vector<Half> expectedOutput0{ 1._h, 2._h };
+    std::vector<Half> expectedOutput1{ 3._h, 4._h };
+
+    std::map<int, std::vector<Half>> inputTensorData = { { 0, inputData } };
+    std::map<int, std::vector<Half>> expectedOutputData = { { 0, expectedOutput0 }, {1, expectedOutput1} };
+
+    EndToEndLayerTestImpl<ArmnnType, ArmnnType>(std::move(net), inputTensorData, expectedOutputData, backends);
+}
+
+template<armnn::DataType ArmnnType>
 void Splitter2dDim0EndToEnd(const std::vector<BackendId>& backends)
 {
     using namespace armnn;
@@ -270,6 +300,55 @@
 }
 
 template<armnn::DataType ArmnnType>
+void Splitter3dDim1EndToEndFloat16(const std::vector<BackendId>& backends)
+{
+    using namespace armnn;
+    using namespace half_float::literal;
+    using Half = half_float::half;
+
+    unsigned int splitAxis = 1;
+    unsigned int numSplit = 2;
+    const TensorShape& inputShape = { 2, 4, 3 };
+    const std::vector<TensorShape> outputShapes{{ 2, 2, 3 }, { 2, 2, 3 }};
+
+    // Builds up the structure of the network
+    INetworkPtr net = CreateSplitterNetwork<ArmnnType>(inputShape, outputShapes, splitAxis, numSplit);
+
+    CHECK(net);
+
+    // Creates structures for input & output.
+    std::vector<Half> inputData{
+            1._h, 2._h, 3._h,
+            4._h, 5._h, 6._h,
+            7._h, 8._h, 9._h,
+            10._h, 11._h, 12._h,
+            13._h, 14._h, 15._h,
+            16._h, 17._h, 18._h,
+            19._h, 20._h, 21._h,
+            22._h, 23._h, 24._h
+    };
+
+    std::vector<Half> expectedOutput0{
+            1._h, 2._h, 3._h,
+            4._h, 5._h, 6._h,
+            13._h, 14._h, 15._h,
+            16._h, 17._h, 18._h
+    };
+    std::vector<Half> expectedOutput1{
+            7._h, 8._h, 9._h,
+            10._h, 11._h, 12._h,
+            19._h, 20._h, 21._h,
+            22._h, 23._h, 24._h
+    };
+
+    std::map<int, std::vector<Half>> inputTensorData = { { 0, inputData } };
+    std::map<int, std::vector<Half>> expectedOutputData = { { 0, expectedOutput0 },
+                                                         { 1, expectedOutput1 } };
+
+    EndToEndLayerTestImpl<ArmnnType, ArmnnType>(std::move(net), inputTensorData, expectedOutputData, backends);
+}
+
+template<armnn::DataType ArmnnType>
 void Splitter3dDim2EndToEnd(const std::vector<BackendId>& backends)
 {
     using namespace armnn;
@@ -549,6 +628,87 @@
     EndToEndLayerTestImpl<ArmnnType, ArmnnType>(std::move(net), inputTensorData, expectedOutputData, backends);
 }
 
+template<armnn::DataType ArmnnType>
+void Splitter4dDim2EndToEndFloat16(const std::vector<BackendId>& backends)
+{
+    using namespace armnn;
+    using namespace half_float::literal;
+    using Half = half_float::half;
+
+    unsigned int splitAxis = 2;
+    unsigned int numSplit = 2;
+    const TensorShape& inputShape = { 2, 3, 4, 2 };
+    const std::vector<TensorShape> outputShapes{{ 2, 3, 2, 2 }, { 2, 3, 2, 2 }};
+
+    // Builds up the structure of the network
+    INetworkPtr net = CreateSplitterNetwork<ArmnnType>(inputShape, outputShapes, splitAxis, numSplit);
+
+    CHECK(net);
+
+    // Creates structures for input & output.
+    std::vector<Half> inputData{
+            1._h, 2._h,
+            3._h, 4._h,
+            5._h, 6._h,
+            7._h, 8._h,
+            9._h, 10._h,
+            11._h, 12._h,
+            13._h, 14._h,
+            15._h, 16._h,
+            17._h, 18._h,
+            19._h, 20._h,
+            21._h, 22._h,
+            23._h, 24._h,
+            25._h, 26._h,
+            27._h, 28._h,
+            29._h, 30._h,
+            31._h, 32._h,
+            33._h, 34._h,
+            35._h, 36._h,
+            37._h, 38._h,
+            39._h, 40._h,
+            41._h, 42._h,
+            43._h, 44._h,
+            45._h, 46._h,
+            47._h, 48._h
+    };
+
+    std::vector<Half> expectedOutput0{
+            1._h, 2._h,
+            3._h, 4._h,
+            9._h, 10._h,
+            11._h, 12._h,
+            17._h, 18._h,
+            19._h, 20._h,
+            25._h, 26._h,
+            27._h, 28._h,
+            33._h, 34._h,
+            35._h, 36._h,
+            41._h, 42._h,
+            43._h, 44._h
+    };
+
+    std::vector<Half> expectedOutput1{
+            5._h, 6._h,
+            7._h, 8._h,
+            13._h, 14._h,
+            15._h, 16._h,
+            21._h, 22._h,
+            23._h, 24._h,
+            29._h, 30._h,
+            31._h, 32._h,
+            37._h, 38._h,
+            39._h, 40._h,
+            45._h, 46._h,
+            47._h, 48._h
+    };
+
+    std::map<int, std::vector<Half>> inputTensorData = {{ 0,inputData }};
+    std::map<int, std::vector<Half>> expectedOutputData = {{ 0, expectedOutput0 }, { 1, expectedOutput1 }};
+
+    EndToEndLayerTestImpl<ArmnnType, ArmnnType>(std::move(net), inputTensorData, expectedOutputData, backends);
+}
+
 template<armnn::DataType ArmnnType, typename T = armnn::ResolveType<ArmnnType>>
 void Splitter4dDim3EndToEnd(const std::vector<BackendId>& backends)
 {
diff --git a/src/backends/reference/test/RefEndToEndTests.cpp b/src/backends/reference/test/RefEndToEndTests.cpp
index 7e07c65..9fb6227 100644
--- a/src/backends/reference/test/RefEndToEndTests.cpp
+++ b/src/backends/reference/test/RefEndToEndTests.cpp
@@ -1084,6 +1084,17 @@
     SpaceToDepthNchwEndToEndTest2(defaultBackends);
 }
 
+// Split
+TEST_CASE("RefSplit1dEndtoEndTestSigned16")
+{
+    Splitter1dEndToEnd<DataType::QSymmS16>(defaultBackends);
+}
+
+TEST_CASE("TosaRefSplit1dEndtoEndTestFloat16")
+{
+    Splitter1dEndToEndFloat16<DataType::Float16>(defaultBackends);
+}
+
 TEST_CASE("RefSplitter1dEndToEndTest")
 {
     Splitter1dEndToEnd<armnn::DataType::Float32>(defaultBackends);
@@ -1114,6 +1125,16 @@
     Splitter2dDim1EndToEnd<armnn::DataType::QAsymmU8>(defaultBackends);
 }
 
+TEST_CASE("RefSplit3dDim1EndtoEndTestSigned16")
+{
+    Splitter3dDim1EndToEnd<DataType::QSymmS16>(defaultBackends);
+}
+
+TEST_CASE("RefSplit3dDim1EndtoEndTestFloat16")
+{
+    Splitter3dDim1EndToEndFloat16<DataType::Float16>(defaultBackends);
+}
+
 TEST_CASE("RefSplitter3dDim0EndToEndTest")
 {
     Splitter3dDim0EndToEnd<armnn::DataType::Float32>(defaultBackends);
@@ -1159,6 +1180,11 @@
     Splitter4dDim2EndToEnd<armnn::DataType::Float32>(defaultBackends);
 }
 
+TEST_CASE("RefSplit4dDim2EndtoEndTestFloat16")
+{
+    Splitter4dDim2EndToEndFloat16<DataType::Float16>(defaultBackends);
+}
+
 TEST_CASE("RefSplitter4dDim3EndToEndTest")
 {
     Splitter4dDim3EndToEnd<armnn::DataType::Float32>(defaultBackends);
diff --git a/src/backends/tosaCommon/TosaMappings.cpp b/src/backends/tosaCommon/TosaMappings.cpp
index 6567026..0bdb1fe 100644
--- a/src/backends/tosaCommon/TosaMappings.cpp
+++ b/src/backends/tosaCommon/TosaMappings.cpp
@@ -84,6 +84,11 @@
             auto sliceDesc = PolymorphicDowncast<const SliceDescriptor*>(&descriptor);
             return ConvertSliceToTosaOperator(layer, inputs, outputs, sliceDesc);
         }
+        case LayerType::Splitter:
+        {
+            auto splitDesc = PolymorphicDowncast<const SplitterDescriptor*>(&descriptor);
+            return ConvertSplitToTosaOperator(layer, inputs, outputs, splitDesc);
+        }
         case LayerType::TransposeConvolution2d:
         {
             auto transposeConv2dDesc = PolymorphicDowncast<const TransposeConvolution2dDescriptor*>(&descriptor);
diff --git a/src/backends/tosaCommon/operatorMappings/CMakeLists.txt b/src/backends/tosaCommon/operatorMappings/CMakeLists.txt
index c864544..279b193 100644
--- a/src/backends/tosaCommon/operatorMappings/CMakeLists.txt
+++ b/src/backends/tosaCommon/operatorMappings/CMakeLists.txt
@@ -24,6 +24,8 @@
         ResizeOperator.cpp
         SliceOperator.hpp
         SliceOperator.cpp
+        SplitOperator.hpp
+        SplitOperator.cpp
         TosaOperatorUtils.hpp
         TransposeConv2dOperator.hpp
         TransposeConv2dOperator.cpp
diff --git a/src/backends/tosaCommon/operatorMappings/SplitOperator.cpp b/src/backends/tosaCommon/operatorMappings/SplitOperator.cpp
new file mode 100644
index 0000000..5231f96
--- /dev/null
+++ b/src/backends/tosaCommon/operatorMappings/SplitOperator.cpp
@@ -0,0 +1,116 @@
+//
+// Copyright © 2023 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+// Copyright © 2020 The TensorFlow Authors. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include "SplitOperator.hpp"
+
+// This function is paraphrased from:
+// tensorflow/compiler/mlir/tosa/transforms/legalize_common.cc from function convertSplitOp
+TosaSerializationBasicBlock* ConvertSplitToTosaOperator(const Layer* layer,
+                                                        const std::vector<const TensorInfo*>& inputs,
+                                                        const std::vector<const TensorInfo*>& outputs,
+                                                        const SplitterDescriptor* splitDescriptor)
+{
+    ARMNN_THROW_INVALIDARG_MSG_IF_FALSE( inputs.size() == 1,
+                                         "ConvertSplitToTosaOperator: Split must have only one input" );
+
+    ARMNN_THROW_INVALIDARG_MSG_IF_FALSE( outputs.size() < 1,
+                                         "ConvertSplitToTosaOperator: Split must have more than one output" );
+
+    if (!inputs[0]->GetShape().AreAllDimensionsSpecified())
+    {
+        throw armnn::Exception("ConvertSplitToTosaOperator: Dynamic input dimensions are unsupported.");
+    }
+
+    std::string inputName = std::string("input0_");
+    std::vector<std::string> outputNames;
+    std::string blockName  = std::string("Op_SPLIT_block_") + GetUniqueTosaMappingID();
+
+    unsigned int numSplit = splitDescriptor->GetNumViews();
+    // 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 tensor names.
+        Layer& connectedLayer = layer->GetInputSlot(0).GetConnectedOutputSlot()->GetOwningLayer();
+        inputName = GenerateUniqueName(connectedLayer, 0);
+
+        for (unsigned int i=0; i < numSplit; ++i)
+        {
+            // Determine unique output(s) tensor name.
+            std::string outputName = GenerateUniqueOutputName(*layer, i);
+            outputNames.push_back(outputName);
+        }
+    }
+    else
+    {
+        for (unsigned int i=0; i < numSplit; ++i)
+        {
+            // Determine unique output(s) tensor name.
+            std::string outputName = "output" + std::to_string(i) + "_";
+            outputNames.push_back(outputName);
+        }
+    }
+
+    // Each slice op has a different beginning point.
+    // The size is the same for each slice op.
+    std::vector<int32_t> beginVals;
+    beginVals.reserve(inputs[0]->GetNumDimensions());
+    std::vector<int32_t> sizeVals;
+    sizeVals.reserve(inputs[0]->GetNumDimensions());
+    for (unsigned int j = 0; j < inputs[0]->GetNumDimensions(); ++j)
+    {
+        beginVals.emplace_back(0);
+        uint32_t dim = inputs[0]->GetShape()[j];
+        sizeVals.emplace_back(dim);
+    }
+
+    uint32_t axis = static_cast<uint32_t>(splitDescriptor->GetAxis());
+    sizeVals[axis] = sizeVals[axis] / static_cast<int32_t>(numSplit);
+
+    std::vector<TosaSerializationOperator*> ops;
+    for (unsigned int i=0; i < numSplit; ++i)
+    {
+        beginVals[axis] = static_cast<int>(i) * sizeVals[axis];
+        TosaSliceAttribute attribute(beginVals, sizeVals);
+        auto* op = new TosaSerializationOperator(Op_SLICE,
+                                                 Attribute_SliceAttribute,
+                                                 &attribute,
+                                                 {inputName},
+                                                 {outputNames[i]});
+
+        ops.push_back(op);
+    }
+
+    std::vector<TosaSerializationTensor*> tensors;
+    // Only add input tensors if connected layer is an input layer.
+    // As intermediate or constant tensors will be created separately.
+    // There also can't be duplicate tensor.
+    if(inputName.find("input0_") != std::string::npos)
+    {
+        std::vector<int32_t> inputShape = GetTosaTensorShape(inputs[0]->GetShape());
+        DType inputDType = ArmNNToDType(inputs[0]->GetDataType());
+
+        tensors.push_back(new TosaSerializationTensor(inputName, inputShape, inputDType, {}));
+    }
+
+    std::vector<int32_t> outputShape = GetTosaTensorShape(outputs[0]->GetShape());
+    DType outputDType = ArmNNToDType(outputs[0]->GetDataType());
+
+    for (unsigned int i=0; i < numSplit; ++i)
+    {
+        tensors.push_back(new TosaSerializationTensor(outputNames[i], 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
+                                           mainName, // region name
+                                           ops, // operators
+                                           tensors, // tensors
+                                           {inputName}, // inputs
+                                           outputNames); // outputs
+}
\ No newline at end of file
diff --git a/src/backends/tosaCommon/operatorMappings/SplitOperator.hpp b/src/backends/tosaCommon/operatorMappings/SplitOperator.hpp
new file mode 100644
index 0000000..93091cd
--- /dev/null
+++ b/src/backends/tosaCommon/operatorMappings/SplitOperator.hpp
@@ -0,0 +1,16 @@
+//
+// Copyright © 2023 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include "TosaOperatorUtils.hpp"
+
+using namespace armnn;
+using namespace tosa;
+
+TosaSerializationBasicBlock* ConvertSplitToTosaOperator(const Layer* layer,
+                                                        const std::vector<const TensorInfo*>& inputs,
+                                                        const std::vector<const TensorInfo*>& outputs,
+                                                        const SplitterDescriptor* splitDescriptor);
\ No newline at end of file
diff --git a/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp b/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp
index 369b37f..749e876 100644
--- a/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp
+++ b/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp
@@ -15,5 +15,6 @@
 #include "ReshapeOperator.hpp"
 #include "ResizeOperator.hpp"
 #include "SliceOperator.hpp"
+#include "SplitOperator.hpp"
 #include "TransposeConv2dOperator.hpp"
 #include "TransposeOperator.hpp"
diff --git a/src/backends/tosaCommon/test/OneToManyMappingTests.cpp b/src/backends/tosaCommon/test/OneToManyMappingTests.cpp
index b8d28f0..94dd537 100644
--- a/src/backends/tosaCommon/test/OneToManyMappingTests.cpp
+++ b/src/backends/tosaCommon/test/OneToManyMappingTests.cpp
@@ -1,9 +1,10 @@
 //
-// Copyright © 2022 Arm Ltd and Contributors. All rights reserved.
+// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 
 #include "AvgPool2DIgnoreValueChecker.hpp"
+#include "SplitChecker.hpp"
 #include <armnn/IRuntime.hpp>
 
 using namespace armnn;
@@ -82,4 +83,69 @@
                               intermediateShape,
                               descriptor);
 }
+
+TEST_CASE("GetTosaMapping_SplitLayer")
+{
+    const unsigned int numViews = 3;
+    const unsigned int numDimensions = 4;
+    armnn::ViewsDescriptor descriptor(numViews, numDimensions);
+    descriptor.SetAxis(static_cast<int32_t>(1));
+
+    std::vector<std::vector<int32_t>> inShape  = {{ 1, 18, 4, 4 }};
+    std::vector<std::vector<int32_t>> outShape = {{ 1, 6, 4, 4 },{ 1, 6, 4, 4 },{ 1, 6, 4, 4 }};
+
+    armnn::TensorInfo inputTensorInfo({1, 18, 4, 4}, DataType::Float32);
+    armnn::TensorInfo outputTensorInfo({1, 6, 4, 4}, DataType::Float32);
+
+    TosaSerializationBasicBlock* basicBlock =
+            GetTosaMapping(nullptr, LayerType::Splitter, {&inputTensorInfo}, {&outputTensorInfo}, descriptor);
+
+    VerifySplit(basicBlock,
+                inShape,
+                outShape,
+                descriptor);
+}
+
+TEST_CASE("GetTosaMappingFromLayer_SplitLayer")
+{
+    IRuntime::CreationOptions options;
+    IRuntimePtr runtime(IRuntime::Create(options));
+
+    // Builds up the structure of the network.
+    INetworkPtr net(INetwork::Create());
+
+    const unsigned int numViews = 3;
+    const unsigned int numDimensions = 4;
+    armnn::ViewsDescriptor descriptor(numViews, numDimensions);
+    descriptor.SetAxis(static_cast<int32_t>(1));
+
+    std::vector<std::vector<int32_t>> inShape  = {{ 1, 18, 4, 4 }};
+    std::vector<std::vector<int32_t>> outShape = {{ 1, 6, 4, 4 },{ 1, 6, 4, 4 },{ 1, 6, 4, 4 }};
+
+    IConnectableLayer* input0   = net->AddInputLayer(0, "input0");
+    IConnectableLayer* split    = net->AddSplitterLayer(descriptor, "split");
+    IConnectableLayer* output0  = net->AddOutputLayer(0, "output0");
+    IConnectableLayer* output1  = net->AddOutputLayer(1, "output1");
+    IConnectableLayer* output2  = net->AddOutputLayer(2, "output2");
+
+    input0->GetOutputSlot(0).Connect(split->GetInputSlot(0));
+    split->GetOutputSlot(0).Connect(output0->GetInputSlot(0));
+    split->GetOutputSlot(1).Connect(output1->GetInputSlot(0));
+    split->GetOutputSlot(2).Connect(output2->GetInputSlot(0));
+
+    armnn::TensorInfo inputTensorInfo({1, 18, 4, 4}, DataType::Float32);
+    armnn::TensorInfo outputTensorInfo({1, 6, 4, 4}, DataType::Float32);
+
+    input0->GetOutputSlot(0).SetTensorInfo(inputTensorInfo);
+    split->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
+    split->GetOutputSlot(1).SetTensorInfo(outputTensorInfo);
+    split->GetOutputSlot(2).SetTensorInfo(outputTensorInfo);
+
+    TosaSerializationBasicBlock* basicBlock = GetTosaMappingFromLayer(PolymorphicDowncast<Layer*>(split));
+
+    VerifySplit(basicBlock,
+                inShape,
+                outShape,
+                descriptor);
+}
 }
\ No newline at end of file
diff --git a/src/backends/tosaCommon/test/SplitChecker.hpp b/src/backends/tosaCommon/test/SplitChecker.hpp
new file mode 100644
index 0000000..edef4a1
--- /dev/null
+++ b/src/backends/tosaCommon/test/SplitChecker.hpp
@@ -0,0 +1,77 @@
+//
+// Copyright © 2023 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "TosaTestUtils.hpp"
+
+using namespace armnn;
+using namespace tosa;
+
+void VerifySplit(TosaSerializationBasicBlock* splitBlock,
+                 std::vector<std::vector<int32_t>> inputShape,
+                 std::vector<std::vector<int32_t>> outputShape,
+                 const BaseDescriptor& splitDescriptor,
+                 DType dataType = DType_FP32)
+{
+    uint32_t numInputs = static_cast<uint32_t>(inputShape.size());
+    uint32_t numOutputs = static_cast<uint32_t>(outputShape.size());
+
+    std::string blockStr = "Op_SPLIT_block_";
+    CHECK(splitBlock->GetName().find(blockStr)  != std::string::npos);
+    CHECK(splitBlock->GetInputs().size() == numInputs);
+    CHECK(splitBlock->GetOutputs().size() == numOutputs);
+    CHECK(splitBlock->GetOperators().size() == 3);
+    CHECK(splitBlock->GetTensors().size() == 4);
+
+    //
+    // Verify slice operator
+    //
+
+    for (uint32_t i = 0; i < splitBlock->GetOperators().size(); i++)
+    {
+        TosaSerializationOperator *sliceOp = splitBlock->GetOperators().at(i);
+        uint32_t sliceOpOutputs = 1;
+        CHECK(sliceOp->GetInputTensorNames().size() == numInputs);
+        CHECK(sliceOp->GetOutputTensorNames().size() == sliceOpOutputs);
+
+        std::basic_string<char> blockInputName = splitBlock->GetInputs()[0];
+        std::basic_string<char> operatorInputName = sliceOp->GetInputTensorNames()[0];
+
+        std::string opInputStr = "input" + std::to_string(0) + "_";
+
+        CHECK(blockInputName == operatorInputName);
+        CHECK(splitBlock->GetTensorByName(blockInputName));
+        CHECK(blockInputName.find(opInputStr) != std::string::npos);
+
+        TosaSerializationTensor* inputTensor = splitBlock->GetTensorByName(operatorInputName);
+        CHECK(inputTensor->GetDtype() == dataType);
+        CHECK(inputTensor->GetData().size() == 0);
+        CHECK(inputTensor->GetShape() == inputShape[0]);
+
+        std::basic_string<char> blockOutputName = splitBlock->GetOutputs()[i];
+        std::basic_string<char> operatorOutputName  = sliceOp->GetOutputTensorNames()[0];
+
+        std::string opOutputStr = "output" + std::to_string(i) + "_";
+
+        CHECK(blockOutputName == operatorOutputName);
+        CHECK(splitBlock->GetTensorByName(blockOutputName));
+        CHECK(blockOutputName.find(opOutputStr)  != std::string::npos);
+
+        TosaSerializationTensor* outputTensor = splitBlock->GetTensorByName(operatorOutputName);
+        CHECK(outputTensor->GetDtype() == dataType);
+        CHECK(outputTensor->GetData().size() == 0);
+        CHECK(outputTensor->GetShape() == outputShape[0]);
+
+        CHECK(sliceOp->GetAttributeType() == Attribute_SliceAttribute);
+        CHECK(sliceOp->GetOp() == Op_SLICE);
+
+        VerifyTosaAttribute(splitDescriptor,
+                            sliceOp->GetAttribute(),
+                            inputShape[0],
+                            outputShape[0],
+                            LayerType::Splitter,
+                            i);
+    }
+
+}
\ No newline at end of file
diff --git a/src/backends/tosaCommon/test/TosaTestUtils.hpp b/src/backends/tosaCommon/test/TosaTestUtils.hpp
index 87ff5ff..05dd164 100644
--- a/src/backends/tosaCommon/test/TosaTestUtils.hpp
+++ b/src/backends/tosaCommon/test/TosaTestUtils.hpp
@@ -193,6 +193,38 @@
 
             break;
         }
+        case LayerType::Splitter:
+        {
+            auto splitDesc = PolymorphicDowncast<const SplitterDescriptor*>(&descriptor);
+            TosaSliceAttribute sliceAttribute(attribute);
+
+            // Each slice op has a different beginning point.
+            // The size is the same for each slice op.
+            std::vector<int32_t> beginVals;
+            beginVals.reserve(inputShape.size());
+            std::vector<int32_t> sizeVals;
+            sizeVals.reserve(inputShape.size());
+            for (unsigned int j = 0; j < inputShape.size(); ++j)
+            {
+                beginVals.emplace_back(0);
+                int32_t dim = inputShape[j];
+                sizeVals.emplace_back(dim);
+            }
+
+            uint32_t axis = static_cast<uint32_t>(splitDesc->GetAxis());
+            sizeVals[axis] = sizeVals[axis] / static_cast<int32_t>(splitDesc->GetNumViews());
+            beginVals[axis] = static_cast<int>(mappingOpNumber) * sizeVals[axis];
+            CHECK(beginVals == sliceAttribute.start());
+            CHECK(sizeVals == sliceAttribute.size());
+
+            CHECK(beginVals.size() == inputShape.size());
+            CHECK(sizeVals.size() == inputShape.size());
+
+            CHECK(beginVals.size() == outputShape.size());
+            CHECK(sizeVals.size() == outputShape.size());
+
+            break;
+        }
         case LayerType::TransposeConvolution2d:
         {
             auto transposeConv2dDesc = PolymorphicDowncast<const TransposeConvolution2dDescriptor*>(&descriptor);
diff --git a/src/backends/tosaReference/TosaRefLayerSupport.cpp b/src/backends/tosaReference/TosaRefLayerSupport.cpp
index 04be52d..ec6fc3b 100644
--- a/src/backends/tosaReference/TosaRefLayerSupport.cpp
+++ b/src/backends/tosaReference/TosaRefLayerSupport.cpp
@@ -74,9 +74,20 @@
         case LayerType::Resize:
         case LayerType::Slice:
         case LayerType::Transpose:
+        {
             inputInfos.push_back(&infos[0]);
             outputInfos.push_back(&infos[1]);
             break;
+        }
+        case LayerType::Splitter:
+        {
+            inputInfos.push_back(&infos[0]);
+            for (unsigned int i = 1; i < infos.size(); ++i)
+            {
+                outputInfos.push_back(&infos[i]);
+            }
+            break;
+        }
         case LayerType::TransposeConvolution2d:
         {
             inputInfos.push_back(&infos[0]); // input
diff --git a/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp b/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp
index b35dacb..ae90c66 100644
--- a/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp
+++ b/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp
@@ -14,6 +14,7 @@
 #include "backendsCommon/test/ResizeEndToEndTestImpl.hpp"
 #include "backendsCommon/test/ElementwiseUnaryEndToEndTestImpl.hpp"
 #include "backendsCommon/test/SliceEndToEndTestImpl.hpp"
+#include "backendsCommon/test/SplitterEndToEndTestImpl.hpp"
 #include "backendsCommon/test/SubtractionEndToEndTestImpl.hpp"
 #include "backendsCommon/test/TransposeConvolution2dEndToEndTestImpl.hpp"
 #include "backendsCommon/test/TransposeEndToEndTestImpl.hpp"
@@ -202,6 +203,129 @@
 {
     SliceEndToEndFloat16<DataType::Float16>(tosaDefaultBackends);
 }
+
+// Split
+TEST_CASE("TosaRefSplit1dEndtoEndTestBoolean")
+{
+    Splitter1dEndToEnd<DataType::Boolean>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit1dEndtoEndTestInt8")
+{
+    Splitter1dEndToEnd<DataType::QSymmS8>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit1dEndtoEndTestSigned16")
+{
+    Splitter1dEndToEnd<DataType::QSymmS16>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit1dEndtoEndTestInt32")
+{
+    Splitter1dEndToEnd<DataType::Signed32>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit1dEndtoEndTestFloat16")
+{
+    Splitter1dEndToEndFloat16<DataType::Float16>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit1dEndToEndFloat32")
+{
+    Splitter1dEndToEnd<DataType::Float32>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit2dDim0EndtoEndTestFloat32")
+{
+    Splitter2dDim0EndToEnd<DataType::Float32>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit2dDim1EndtoEndTestFloat32")
+{
+    Splitter2dDim1EndToEnd<DataType::Float32>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit3dDim0EndtoEndTestFloat32")
+{
+    Splitter3dDim0EndToEnd<DataType::Float32>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit3dDim1EndtoEndTestFloat32")
+{
+    Splitter3dDim1EndToEnd<DataType::Float32>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit3dDim1EndtoEndTestFloat16")
+{
+    Splitter3dDim1EndToEndFloat16<DataType::Float16>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit3dDim1EndtoEndTestBoolean")
+{
+    Splitter3dDim1EndToEnd<DataType::Boolean>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit3dDim1EndtoEndTestInt8")
+{
+    Splitter3dDim1EndToEnd<DataType::QSymmS8>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit3dDim1EndtoEndTestSigned16")
+{
+    Splitter3dDim1EndToEnd<DataType::QSymmS16>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit3dDim1EndtoEndTestInt32")
+{
+    Splitter3dDim1EndToEnd<DataType::Signed32>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit3dDim2EndtoEndTestInt8")
+{
+    Splitter3dDim2EndToEnd<DataType::QAsymmS8>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit4dDim0EndtoEndTestInt8")
+{
+    Splitter4dDim0EndToEnd<DataType::QSymmS8>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit4dDim1EndtoEndTestInt8")
+{
+    Splitter4dDim1EndToEnd<DataType::QSymmS8>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit4dDim2EndtoEndTestBoolean")
+{
+    Splitter4dDim2EndToEnd<DataType::Boolean>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit4dDim2EndtoEndTestInt8")
+{
+    Splitter4dDim2EndToEnd<DataType::QSymmS8>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit4dDim2EndtoEndTestInt16")
+{
+    Splitter4dDim2EndToEnd<DataType::QSymmS16>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit4dDim2EndtoEndTestInt32")
+{
+    Splitter4dDim2EndToEnd<DataType::Signed32>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit4dDim2EndtoEndTestFloat16")
+{
+    Splitter4dDim2EndToEndFloat16<DataType::Float16>(tosaDefaultBackends);
+}
+
+TEST_CASE("TosaRefSplit4dDim3EndtoEndTestInt8")
+{
+    Splitter4dDim3EndToEnd<DataType::QSymmS8>(tosaDefaultBackends);
+}
+
+// Subtraction
 TEST_CASE("TosaRefSubtractionEndtoEndTestFloat32")
 {
     SubtractionEndToEnd<DataType::Float32>(tosaDefaultBackends);
diff --git a/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp b/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp
index fb4c84f..6f038ab 100644
--- a/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp
+++ b/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp
@@ -484,6 +484,54 @@
     CHECK(!supported);
 }
 
+TEST_CASE("IsLayerSupportedTosaReferenceSplit")
+{
+    TensorShape inShape = {1, 18, 4, 4};
+    TensorShape outShape = {1, 6, 4, 4};
+    TensorInfo in(inShape, DataType::Float32);
+    TensorInfo out(outShape, DataType::Float32);
+
+    const unsigned int numViews = 3;
+    const unsigned int numDimensions = 4;
+    armnn::SplitterDescriptor descriptor(numViews, numDimensions);
+    descriptor.SetAxis(static_cast<int32_t>(1));
+
+    TosaRefLayerSupport supportChecker;
+    std::string reasonIfNotSupported;
+    auto supported = supportChecker.IsLayerSupported(LayerType::Splitter,
+                                                     {in, out},
+                                                     descriptor,
+                                                     EmptyOptional(),
+                                                     EmptyOptional(),
+                                                     reasonIfNotSupported);
+
+    CHECK(supported);
+}
+
+TEST_CASE("IsLayerSupportedTosaReferenceSplitUnsupported")
+{
+    TensorShape inShape = {1, 18, 4, 4};
+    TensorShape outShape = {1, 6, 4, 4};
+    TensorInfo in(inShape, DataType::Signed64);
+    TensorInfo out(outShape, DataType::Signed64);
+
+    const unsigned int numViews = 3;
+    const unsigned int numDimensions = 4;
+    armnn::SplitterDescriptor descriptor(numViews, numDimensions);
+    descriptor.SetAxis(static_cast<int32_t>(1));
+
+    TosaRefLayerSupport supportChecker;
+    std::string reasonIfNotSupported;
+    auto supported = supportChecker.IsLayerSupported(LayerType::Splitter,
+                                                     {in, out},
+                                                     descriptor,
+                                                     EmptyOptional(),
+                                                     EmptyOptional(),
+                                                     reasonIfNotSupported);
+
+    CHECK(!supported);
+}
+
 TEST_CASE("IsLayerSupportedTosaReferenceSubtraction")
 {
     TensorShape shape0 = {1,1,3,4};
diff --git a/src/backends/tosaReference/workloads/TosaRefPreCompiledWorkload.cpp b/src/backends/tosaReference/workloads/TosaRefPreCompiledWorkload.cpp
index 5e4103a..26bd29c 100644
--- a/src/backends/tosaReference/workloads/TosaRefPreCompiledWorkload.cpp
+++ b/src/backends/tosaReference/workloads/TosaRefPreCompiledWorkload.cpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2022 Arm Ltd and Contributors. All rights reserved.
+// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 
diff --git a/src/backends/tosaReference/workloads/TosaRefPreCompiledWorkload.hpp b/src/backends/tosaReference/workloads/TosaRefPreCompiledWorkload.hpp
index 337e8f9..1ea4d8d 100644
--- a/src/backends/tosaReference/workloads/TosaRefPreCompiledWorkload.hpp
+++ b/src/backends/tosaReference/workloads/TosaRefPreCompiledWorkload.hpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2022 Arm Ltd and Contributors. All rights reserved.
+// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //