IVGCVSW-7344  Add LeakyRelu Activation support to TOSA Reference Backend

  * Adding a one to many FP32 tosa mapping for Leaky Relu
  * Added a few utilities that are needed
  * Added new tests

Signed-off-by: Tracy Narine <tracy.narine@arm.com>
Change-Id: If1d7c57a523961581777a244416a7346a9310803
diff --git a/src/backends/backendsCommon/test/ActivationEndToEndTestImpl.hpp b/src/backends/backendsCommon/test/ActivationEndToEndTestImpl.hpp
index c6d49b1..996e760 100644
--- a/src/backends/backendsCommon/test/ActivationEndToEndTestImpl.hpp
+++ b/src/backends/backendsCommon/test/ActivationEndToEndTestImpl.hpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2020-2021,2023 Arm Ltd and Contributors. All rights reserved.
+// Copyright © 2020-2021,2023-2024 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 #pragma once
@@ -171,4 +171,31 @@
                                       descriptor);
 }
 
-} // anonymous namespace
\ No newline at end of file
+/** Executes an end to end test for Leaky Relu activation with specific input and expected-output data
+ *
+ * @tparam ArmnnType  The armnn data type for the input and expected-output data
+ * @param backends  The backends on which to run the test
+ */
+template<armnn::DataType ArmnnType>
+void LeakyReluEndToEndTest(const std::vector<BackendId>& backends, const float qScale=1.0f, const int32_t qOffset=0)
+{
+    std::vector<float> floatInputData{ -2.0f, -1.0f, -0.0f, 0.0f,
+                                        1.0f,  2.0f,  3.0f, 4.0f };
+
+    std::vector<float> floatExpectedOutputData{ -0.02f, -0.01f, -0.0f, 0.0f,
+                                                 1.0f,   2.0f,   3.0f, 4.0f };
+
+    armnn::TensorInfo inputInfo({ 2, 2, 2, 1 }, ArmnnType, qScale, qOffset, true);
+    armnn::TensorInfo outputInfo({ 2, 2, 2, 1 }, ArmnnType, qScale, qOffset);
+
+    armnn::ActivationDescriptor descriptor(ActivationFunction::LeakyReLu, static_cast<float>(0.01));
+
+    ActivationEndToEndImpl<ArmnnType>(backends,
+                                      floatInputData,
+                                      floatExpectedOutputData,
+                                      inputInfo,
+                                      outputInfo,
+                                      descriptor);
+}
+
+} // anonymous namespace
diff --git a/src/backends/reference/test/RefEndToEndTests.cpp b/src/backends/reference/test/RefEndToEndTests.cpp
index 78852bc..63f76b1 100644
--- a/src/backends/reference/test/RefEndToEndTests.cpp
+++ b/src/backends/reference/test/RefEndToEndTests.cpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2017-2023 Arm Ltd and Contributors. All rights reserved.
+// Copyright © 2017-2024 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 
@@ -1044,6 +1044,32 @@
     HardSwishEndToEndTest<armnn::DataType::QSymmS16>(defaultBackends);
 }
 
+// LeakyRelu
+TEST_CASE("RefLeakyReluActivationFloat32")
+{
+    LeakyReluEndToEndTest<DataType::Float32>(defaultBackends);
+}
+
+TEST_CASE("RefLeakyReluActivationFloat16")
+{
+    LeakyReluEndToEndTest<DataType::Float16>(defaultBackends, 0.3f, 5);
+}
+
+TEST_CASE("RefLeakyReluActivationInt8")
+{
+    LeakyReluEndToEndTest<DataType::QAsymmS8>(defaultBackends, 0.6f, 7);
+}
+
+TEST_CASE("RefLeakyReluActivationUInt8")
+{
+    LeakyReluEndToEndTest<DataType::QAsymmU8>(defaultBackends, 0.12f, 15);
+}
+
+TEST_CASE("RefLeakyReluActivationInt16")
+{
+    LeakyReluEndToEndTest<DataType::QSymmS16>(defaultBackends, 0.15f, 55);
+}
+
 // LogSoftmax
 TEST_CASE("RefLogSoftmaxEndToEndTest")
 {
diff --git a/src/backends/tosaCommon/TosaMappings.cpp b/src/backends/tosaCommon/TosaMappings.cpp
index 6c6bff4..1ebb68b 100644
--- a/src/backends/tosaCommon/TosaMappings.cpp
+++ b/src/backends/tosaCommon/TosaMappings.cpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved.
+// Copyright © 2022-2024 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 
@@ -23,6 +23,18 @@
 {
     switch (type)
     {
+        case LayerType::Activation:
+        {
+            auto activationDesc = PolymorphicDowncast<const ActivationDescriptor*>(&descriptor);
+            if (activationDesc->m_Function == ActivationFunction::LeakyReLu)
+            {
+                return ConvertActivationToTosaOperator(layer, inputs, outputs, activationDesc);
+            }
+            else
+            {
+                return CreateEmptyTosaSerializationBasicBlock();
+            }
+        }
         case LayerType::Addition:
         case LayerType::Multiplication:
         case LayerType::Subtraction:
diff --git a/src/backends/tosaCommon/operatorMappings/ActivationOperator.cpp b/src/backends/tosaCommon/operatorMappings/ActivationOperator.cpp
new file mode 100644
index 0000000..4009ae8
--- /dev/null
+++ b/src/backends/tosaCommon/operatorMappings/ActivationOperator.cpp
@@ -0,0 +1,284 @@
+//
+// Copyright © 2024 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 "ActivationOperator.hpp"
+#include "TosaRescaleOperatorUtils.hpp"
+
+#include <layers/ActivationLayer.hpp>
+
+// This function is paraphrased from:
+// tensorflow/compiler/mlir/tosa/transforms/legalize_tfl.cc from function ConvertTFLLeakyReluOp
+TosaSerializationBasicBlock* ConvertActivationToTosaOperator(const Layer* layer,
+                                                             const std::vector<const TensorInfo*>& inputs,
+                                                             const std::vector<const TensorInfo*>& outputs,
+                                                             const ActivationDescriptor* activationDescriptor)
+{
+    if (inputs.size() != 1)
+    {
+        throw armnn::Exception("ConvertActivationToTosaOperator: 1 input tensors required.");
+    }
+
+    if (outputs.size() != 1)
+    {
+        throw armnn::Exception("ConvertActivationToTosaOperator: 1 output tensor required.");
+    }
+
+    std::string inputName       = std::string("input0_");
+    std::string outputNameAlpha = std::string("intermediate1_") + GetUniqueTosaMappingID();
+    std::string outputNameMul   = std::string("intermediate2_") + GetUniqueTosaMappingID();
+    std::string outputName      = std::string("output0_");
+    std::string blockName       = std::string("Op_ACTIVATION_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 tensors names.
+        Layer& connectedInputLayer = layer->GetInputSlot(0).GetConnectedOutputSlot()->GetOwningLayer();
+        inputName = GenerateUniqueName(connectedInputLayer, 0);
+
+        // Determine unique output tensor name.
+        outputName = GenerateUniqueOutputName(*layer, 0);
+    }
+
+    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.
+    std::vector<int32_t> inputShape0;
+    DType inputDType0 =  DType::DType_UNKNOWN;
+    if(inputName.find("input0_") != std::string::npos)
+    {
+        inputShape0 = GetTosaTensorShape(inputs[0]->GetShape());
+        inputDType0 = ArmNNToDType(inputs[0]->GetDataType());
+        tensors.push_back(new TosaSerializationTensor(inputName, inputShape0, inputDType0, {}));
+    }
+
+    std::vector<int32_t> outputShape0 = GetTosaTensorShape(outputs[0]->GetShape());
+    DType outputDType0 = ArmNNToDType(outputs[0]->GetDataType());
+    tensors.push_back(new TosaSerializationTensor(outputName, outputShape0, outputDType0, {}));
+
+#if TOSA_FWD_COMPAT_VERSION(0, 60, 0)
+    std::string outputNameMAXMIN= std::string("intermediate3_") + GetUniqueTosaMappingID();
+
+    if (inputDType0 == DType::DType_FP32)
+    {
+        // const_alpha
+        TosaSerializationOperator* alphaOp = nullptr;
+        TosaSerializationTensor* alphaTensor = nullptr;
+        CreateConstTosaOperator<float>(outputNameAlpha,
+                                       activationDescriptor->m_A,
+                                       inputDType0,
+                                       inputShape0,
+                                       alphaOp,
+                                       alphaTensor);
+        tensors.push_back(alphaTensor);
+
+        // mul
+        int32_t shift = 0;
+        TosaMulAttribute mulAttribute(shift);
+        TosaSerializationOperator* mulOp = new TosaSerializationOperator(Op_MUL,
+                                                                         Attribute_MulAttribute,
+                                                                         &mulAttribute,
+                                                                         {inputName, outputNameAlpha},
+                                                                         {outputNameMul});
+        tensors.push_back(new TosaSerializationTensor(outputNameMul, inputShape0, inputDType0, {}));
+
+        TosaSerializationOperator* op = nullptr;
+        if (activationDescriptor->m_A <= 1.0)
+        {
+            op = new TosaSerializationOperator(Op_MAXIMUM,
+                                               Attribute_NONE,
+                                               nullptr,
+                                               {inputName, outputNameMul},
+                                               {outputName});
+        }
+        else
+        {
+            op = new TosaSerializationOperator(Op_MINIMUM,
+                                               Attribute_NONE,
+                                               nullptr,
+                                               {inputName, outputNameMul},
+                                               {outputName});
+
+        }
+
+        // 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
+                                               {alphaOp, mulOp, op},   // operators
+                                               tensors,                // tensors
+                                               {inputName},            // inputs
+                                               {outputName});          // outputs
+    }
+    else
+    {
+        std::string outputNameRescaleAlpha      = std::string("intermediate3_") + GetUniqueTosaMappingID();
+        std::string outputNameRescaleIdentity   = std::string("intermediate4_") + GetUniqueTosaMappingID();
+        std::string outputNameRescaleMaxMin     = std::string("intermediate5_") + GetUniqueTosaMappingID();
+
+        DType rescale_type      = DType::DType_INT32;
+        float alpha             = activationDescriptor->m_A;
+        double scale_alpha      = inputs[0]->GetQuantizationScale() * alpha / outputs[0]->GetQuantizationScale();
+        double scale_identity   = inputs[0]->GetQuantizationScale() / outputs[0]->GetQuantizationScale();
+        int32_t input_zp        = inputs[0]->GetQuantizationOffset();
+        int32_t output_zp       = outputs[0]->GetQuantizationOffset();
+
+        // Value op_rescale_alpha_in =
+        //        buildRescale(rewriter, op, rescale_type, input, scale_alpha,
+        //                     input_qtype.getZeroPoint(), 0, true, true);
+        TosaSerializationOperator* rescaleAlphaOp = nullptr;
+        TosaSerializationTensor* rescaleAlphaTensor = nullptr;
+        CreateRescaleTosaOperator(inputName,
+                                  outputNameRescaleAlpha,
+                                  rescale_type,
+                                  inputShape0,
+                                  scale_alpha,
+                                  input_zp,
+                                  0,
+                                  true,
+                                  true,
+                                  &rescaleAlphaOp,
+                                  &rescaleAlphaTensor);
+        tensors.push_back(rescaleAlphaTensor);
+
+        // Value op_rescale_identity_in =
+        //       buildRescale(rewriter, op, rescale_type, input, scale_identity,
+        //                    input_qtype.getZeroPoint(), 0, true, true);
+        TosaSerializationOperator* rescaleIdentityOp = nullptr;
+        TosaSerializationTensor* rescaleIdentityTensor = nullptr;
+        CreateRescaleTosaOperator(inputName,
+                                  outputNameRescaleIdentity,
+                                  rescale_type,
+                                  inputShape0,
+                                  scale_identity,
+                                  input_zp,
+                                  0,
+                                  true,
+                                  true,
+                                  &rescaleIdentityOp,
+                                  &rescaleIdentityTensor);
+        tensors.push_back(rescaleIdentityTensor);
+
+        // Value result_int32;
+        // if (alpha <= 1.0) {
+        //    auto max_op = CreateOpAndInfer<tosa::MaximumOp>(
+        //            rewriter, op->getLoc(), rescale_type, op_rescale_identity_in,
+        //            op_rescale_alpha_in);
+        //    result_int32 = max_op.getResult();
+        // } else {
+        //    auto min_op = CreateOpAndInfer<tosa::MinimumOp>(
+        //            rewriter, op->getLoc(), rescale_type, op_rescale_identity_in,
+        //            op_rescale_alpha_in);
+        //    result_int32 = min_op.getResult();
+        // }
+        TosaSerializationOperator* op = nullptr;
+        if (alpha <= 1.0)
+        {
+            op = new TosaSerializationOperator(Op_MAXIMUM,
+                                               Attribute_NONE,
+                                               nullptr,
+                                               {outputNameRescaleAlpha, outputNameRescaleIdentity},
+                                               {outputNameRescaleMaxMin});
+        }
+        else
+        {
+            op = new TosaSerializationOperator(Op_MINIMUM,
+                                               Attribute_NONE,
+                                               nullptr,
+                                               {outputNameRescaleAlpha, outputNameRescaleIdentity},
+                                               {outputNameRescaleMaxMin});
+
+        }
+        tensors.push_back(new TosaSerializationTensor(outputNameRescaleMaxMin, inputShape0, rescale_type, {}));
+
+        // Value output = buildRescaleFromInt32(rewriter, op, output_type, result_int32,
+        //                                      1.0, output_qtype.getZeroPoint());
+        TosaSerializationOperator* rescaleOutputOp = nullptr;
+        CreateFromInt32RescaleTosaOperator(outputNameRescaleMaxMin,
+                                           outputName,
+                                           outputDType0,
+                                           outputShape0,
+                                           1.0,
+                                           output_zp,
+                                           &rescaleOutputOp,
+                                           nullptr);
+
+        // 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
+                                               {rescaleAlphaOp, rescaleIdentityOp, op, rescaleOutputOp}, // operators
+                                               tensors,                // tensors
+                                               {inputName},            // inputs
+                                               {outputName});          // outputs
+    }
+#else
+    std::string outputNameZero  = std::string("intermediate3_") + GetUniqueTosaMappingID();
+    std::string outputNameGE    = std::string("intermediate4_") + GetUniqueTosaMappingID();
+
+    // const_zero
+    TosaSerializationOperator* zeroOp = nullptr;
+    TosaSerializationTensor* zeroTensor = nullptr;
+    CreateConstTosaOperator<float>(outputNameZero,
+                                   0.0f,
+                                   inputDType0,
+                                   inputShape0,
+                                   zeroOp,
+                                   zeroTensor);
+    tensors.push_back(zeroTensor);
+
+    // const_alpha
+    TosaSerializationOperator* alphaOp = nullptr;
+    TosaSerializationTensor* alphaTensor = nullptr;
+    CreateConstTosaOperator<float>(outputNameAlpha,
+                                   activationDescriptor->m_A,
+                                   inputDType0,
+                                   inputShape0,
+                                   alphaOp,
+                                   alphaTensor);
+    tensors.push_back(alphaTensor);
+
+    // mul
+    int32_t shift = 0;
+    TosaMulAttribute mulAttribute(shift);
+    TosaSerializationOperator* mulOp = new TosaSerializationOperator(Op_MUL,
+                                                                     Attribute_MulAttribute,
+                                                                     &mulAttribute,
+                                                                     {inputName, outputNameAlpha},
+                                                                     {outputNameMul});
+    tensors.push_back(new TosaSerializationTensor(outputNameMul, inputShape0, inputDType0, {}));
+
+    // greater_equal
+    TosaSerializationOperator* geOp = new TosaSerializationOperator(Op_GREATER_EQUAL,
+                                                                    Attribute_NONE,
+                                                                    nullptr,
+                                                                    {inputName, outputNameZero},
+                                                                    {outputNameGE});
+    tensors.push_back(new TosaSerializationTensor(outputNameGE, outputShape0, DType::DType_BOOL, {}));
+
+    // select
+    TosaSerializationOperator* selOp = new TosaSerializationOperator(Op_SELECT,
+                                                                     Attribute_NONE,
+                                                                     nullptr,
+                                                                     {outputNameGE, inputName, outputNameMul},
+                                                                     {outputName});
+
+    // 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
+                                           {zeroOp, alphaOp, mulOp, geOp, selOp},   // operators
+                                           tensors,                                 // tensors
+                                           {inputName},                             // inputs
+                                           {outputName});                           // outputs
+#endif
+}
diff --git a/src/backends/tosaCommon/operatorMappings/ActivationOperator.hpp b/src/backends/tosaCommon/operatorMappings/ActivationOperator.hpp
new file mode 100644
index 0000000..7519f0c
--- /dev/null
+++ b/src/backends/tosaCommon/operatorMappings/ActivationOperator.hpp
@@ -0,0 +1,20 @@
+//
+// Copyright © 2024 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include <Layer.hpp>
+
+#include <tosa_serialization_handler.h>
+
+#include "TosaOperatorUtils.hpp"
+
+using namespace armnn;
+using namespace tosa;
+
+TosaSerializationBasicBlock* ConvertActivationToTosaOperator(const Layer* layer,
+                                                             const std::vector<const TensorInfo*>& inputs,
+                                                             const std::vector<const TensorInfo*>& outputs,
+                                                             const ActivationDescriptor* activationDescriptor);
diff --git a/src/backends/tosaCommon/operatorMappings/CMakeLists.txt b/src/backends/tosaCommon/operatorMappings/CMakeLists.txt
index b694634..58a6457 100644
--- a/src/backends/tosaCommon/operatorMappings/CMakeLists.txt
+++ b/src/backends/tosaCommon/operatorMappings/CMakeLists.txt
@@ -1,9 +1,11 @@
 #
-# Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved.
+# Copyright © 2022-2024 Arm Ltd and Contributors. All rights reserved.
 # SPDX-License-Identifier: MIT
 #
 
 list(APPEND armnnTosaBackendOperators_sources
+        ActivationOperator.hpp
+        ActivationOperator.cpp
         AvgPool2DIgnoreValueOperator.hpp
         AvgPool2DIgnoreValueOperator.cpp
         ConcatOperator.hpp
@@ -29,6 +31,7 @@
         SplitOperator.hpp
         SplitOperator.cpp
         TosaOperatorUtils.hpp
+        TosaRescaleOperatorUtils.hpp
         TransposeConv2dOperator.hpp
         TransposeConv2dOperator.cpp
         TransposeOperator.hpp
diff --git a/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp b/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp
index 3d88b65..fd0574f 100644
--- a/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp
+++ b/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp
@@ -1,10 +1,11 @@
 //
-// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved.
+// Copyright © 2022-2024 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 
 #pragma once
 
+#include "ActivationOperator.hpp"
 #include "AvgPool2DIgnoreValueOperator.hpp"
 #include "ConcatOperator.hpp"
 #include "ConstantOperator.hpp"
diff --git a/src/backends/tosaCommon/operatorMappings/TosaOperatorUtils.hpp b/src/backends/tosaCommon/operatorMappings/TosaOperatorUtils.hpp
index e43f6ca..05ccef4 100644
--- a/src/backends/tosaCommon/operatorMappings/TosaOperatorUtils.hpp
+++ b/src/backends/tosaCommon/operatorMappings/TosaOperatorUtils.hpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved.
+// Copyright © 2022-2024 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 
@@ -12,6 +12,7 @@
 #include "common/include/ProfilingGuid.hpp"
 
 #include <tosa_serialization_handler.h>
+#include <version.h>
 
 using namespace armnn;
 using namespace tosa;
@@ -355,7 +356,6 @@
     return uint8Data;
 }
 
-
 inline std::vector<uint8_t> CreateConstTosaData(const void* value,
                                                 DType dtype,
                                                 const std::vector<int32_t>& shape)
@@ -452,4 +452,10 @@
 
     tensor = new TosaSerializationTensor(outputName, shape, dtype, uint8Data);
     ARMNN_THROW_MSG_IF_FALSE(tensor, armnn::Exception, "CreateConstTosaOperator: failed to created tensor");
-}
\ No newline at end of file
+}
+
+// Macro to conditionally compile Tosa code
+#define TOSA_FWD_COMPAT_VERSION(_major, _minor, _patch) \
+        (TOSA_REFERENCE_MODEL_VERSION_MAJOR >= _major) && \
+        (TOSA_REFERENCE_MODEL_VERSION_MINOR >= _minor) && \
+        (TOSA_REFERENCE_MODEL_VERSION_PATCH >= _patch)
diff --git a/src/backends/tosaCommon/operatorMappings/TosaRescaleOperatorUtils.hpp b/src/backends/tosaCommon/operatorMappings/TosaRescaleOperatorUtils.hpp
new file mode 100644
index 0000000..a043284
--- /dev/null
+++ b/src/backends/tosaCommon/operatorMappings/TosaRescaleOperatorUtils.hpp
@@ -0,0 +1,130 @@
+//
+// Copyright © 2024 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include <armnn/Exceptions.hpp>
+
+#pragma once
+
+inline void CreateRescaleTosaOperator(const std::string& inputName,
+                                      const std::string& outputName,
+                                      DType output_type,
+                                      const std::vector<int32_t>& shape,
+                                      int32_t scale_multiplier,
+                                      int32_t scale_shift,
+                                      int32_t input_zp,
+                                      int32_t output_zp,
+                                      bool double_round,
+                                      bool scale32,
+                                      TosaSerializationOperator** op,
+                                      TosaSerializationTensor** tensor)
+{
+    if (!op)
+    {
+        throw armnn::Exception("CreateRescaleTosaOperator: nullptr op");
+    }
+
+    std::vector<int32_t> multipliers{scale_multiplier};
+    std::vector<int32_t> shifts{scale_shift};
+    TosaRescaleAttribute attribute(input_zp,
+                                   output_zp,
+                                   multipliers,
+                                   shifts,
+                                   scale32,
+                                   double_round,
+                                   false);
+
+    // op
+    *op = new TosaSerializationOperator(Op_RESCALE, Attribute_RescaleAttribute, &attribute, {inputName}, {outputName});
+    if (!(*op))
+    {
+        throw armnn::Exception("CreateRescaleTosaOperator: failed to created operator");
+    }
+    if (tensor != nullptr)
+    {
+        // tensor
+        *tensor = new TosaSerializationTensor(outputName, shape, output_type, {});
+        if (! (*tensor))
+        {
+            throw armnn::Exception("CreateRescaleTosaOperator: failed to created tensor");
+        }
+    }
+}
+
+inline void CreateRescaleTosaOperator(const std::string& inputName,
+                                      const std::string& outputName,
+                                      DType output_type,
+                                      const std::vector<int32_t>& shape,
+                                      double scale,
+                                      int32_t input_zp,
+                                      int32_t output_zp,
+                                      bool double_round,
+                                      bool scale32,
+                                      TosaSerializationOperator** op,
+                                      TosaSerializationTensor** tensor)
+{
+    //  The code that follows is based on the behaviour specified in
+    //  https://www.mlplatform.org/tosa/tosa_spec.html#_precision_scaling
+
+    auto GetScaleParams = [](double scale, double& m, int32_t& n)
+    {
+        m = 0;
+        n = 0;
+
+        double lastErr = 1e06;
+
+        const int32_t numExponents = 62;
+        const double start = 1.0;
+        const double end = 2.0;
+
+        // Slow iterative approach but running in Reference only
+        for (int32_t i = 0; i < numExponents; ++i)
+        {
+            double exp = 1.0 / (1 << i);
+            double currentM = scale / exp;    // Find current m given value = currentM  * exp
+            if ((currentM >= start) && (currentM < end))
+            {
+                double value = currentM * exp;
+                double err = std::abs(scale - value);
+                if (err < lastErr)
+                {
+                    // Take the m, n that minimize the error
+                    n = i;
+                    m = currentM;
+                    lastErr = err;
+                }
+            }
+        }
+    };
+
+    auto GetMultiplierShiftByScale = [GetScaleParams](bool scale32, double scale, int32_t& multiplier, int32_t& shift)
+    {
+        double m = 0;
+        int32_t n = 0;
+
+        GetScaleParams(scale, m, n);
+
+        multiplier  = (scale32) ? (1 << 30) * static_cast<int32_t>(m) : (1 << 14) * static_cast<int32_t>(m);
+        shift       = (scale32) ? (30 + n) : (14 + n);
+    };
+
+    int32_t multiplier;
+    int32_t shift;
+    GetMultiplierShiftByScale(scale32, scale, multiplier, shift);
+    CreateRescaleTosaOperator(inputName, outputName, output_type, shape, multiplier, shift,
+                              input_zp, output_zp, double_round, scale32, op, tensor);
+}
+
+inline void CreateFromInt32RescaleTosaOperator(const std::string& inputName,
+                                               const std::string& outputName,
+                                                DType output_type,
+                                                const std::vector<int32_t>& shape,
+                                                double output_scale,
+                                                int32_t output_zp,
+                                                TosaSerializationOperator** op,
+                                                TosaSerializationTensor** tensor)
+{
+    CreateRescaleTosaOperator(inputName, outputName, output_type, shape,
+                              output_scale, 0, output_zp, true, true, op, tensor);
+}
diff --git a/src/backends/tosaCommon/test/OneToManyMappingTests.cpp b/src/backends/tosaCommon/test/OneToManyMappingTests.cpp
index f439b04..dde4d79 100644
--- a/src/backends/tosaCommon/test/OneToManyMappingTests.cpp
+++ b/src/backends/tosaCommon/test/OneToManyMappingTests.cpp
@@ -1,12 +1,12 @@
 //
-// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved.
+// Copyright © 2022-2024 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 
 #include "AvgPool2DIgnoreValueChecker.hpp"
 #include "QuantizeChecker.hpp"
 #include "SplitChecker.hpp"
-
+#include <backendsCommon/test/ActivationEndToEndTestImpl.hpp>
 #include <armnn/IRuntime.hpp>
 
 using namespace armnn;
@@ -190,4 +190,52 @@
                 outShape,
                 descriptor);
 }
-}
\ No newline at end of file
+
+// Activation
+
+static std::vector<BackendId> tosaDefaultBackends = { "TosaRef" };
+
+TEST_CASE("GetTosaMapping_ActivationFloat32")
+{
+    LeakyReluEndToEndTest<DataType::Float32>(tosaDefaultBackends);
+}
+
+TEST_CASE("GetTosaMapping_ActivationFloat16")
+{
+    try
+    {
+        LeakyReluEndToEndTest<DataType::Float16>(tosaDefaultBackends);
+    }
+    catch (armnn::Exception& e)
+    {
+        CHECK_EQ(std::string(e.what()), "Failed to assign a backend to each layer");
+    }
+}
+
+TEST_CASE("GetTosaMapping_ActivationInt32")
+{
+    LeakyReluEndToEndTest<DataType::Signed32>(tosaDefaultBackends, 0.15f, 0);
+}
+
+TEST_CASE("GetTosaMapping_ActivationInt16")
+{
+    LeakyReluEndToEndTest<DataType::QSymmS16>(tosaDefaultBackends, 0.35f, 0);
+}
+
+TEST_CASE("GetTosaMapping_ActivationInt8")
+{
+    LeakyReluEndToEndTest<DataType::QSymmS8>(tosaDefaultBackends, 0.75f, 0);
+}
+
+TEST_CASE("GetTosaMapping_ActivationUInt8")
+{
+    try
+    {
+        LeakyReluEndToEndTest<DataType::QAsymmU8>(tosaDefaultBackends);
+    }
+    catch (armnn::Exception& e)
+    {
+        CHECK_EQ(std::string(e.what()), "Failed to assign a backend to each layer");
+    }
+}
+}
diff --git a/src/backends/tosaReference/TosaRefLayerSupport.cpp b/src/backends/tosaReference/TosaRefLayerSupport.cpp
index a38c431..dac0667 100644
--- a/src/backends/tosaReference/TosaRefLayerSupport.cpp
+++ b/src/backends/tosaReference/TosaRefLayerSupport.cpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved.
+// Copyright © 2022-2024 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 
@@ -34,6 +34,10 @@
 
     switch (type)
     {
+        case LayerType::Activation:
+            inputInfos.push_back(&infos[0]);
+            outputInfos.push_back(&infos[1]);
+            break;
         case LayerType::Input:
         case LayerType::Output:
             return true;
diff --git a/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp b/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp
index 63fa69d..7a3edaf 100644
--- a/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp
+++ b/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp
@@ -25,7 +25,7 @@
 
 TEST_SUITE("TosaRefEndToEnd")
 {
-std::vector<BackendId> tosaDefaultBackends = { "TosaRef" };
+static std::vector<BackendId> tosaDefaultBackends = { "TosaRef" };
 
 // Addition
 TEST_CASE("TosaRefAdditionEndtoEndTestFloat32")
diff --git a/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp b/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp
index 6f038ab..2da2875 100644
--- a/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp
+++ b/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved.
+// Copyright © 2022-2024 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 
@@ -287,6 +287,46 @@
     CHECK(supported);
 }
 
+TEST_CASE("IsLayerSupportedTosaReferenceLeakyReLuActivation")
+{
+    TensorInfo inputInfo1({1,1,3,4}, DataType::Float32);
+    TensorInfo inputInfo2({1,1,3,4}, DataType::Float32);
+    TensorInfo outputInfo({1,1,3,4}, DataType::Float32);
+
+    TosaRefLayerSupport supportChecker;
+    std::string reasonIfNotSupported;
+    ActivationDescriptor descriptor;
+    descriptor.m_Function = ActivationFunction::LeakyReLu;
+    auto supported = supportChecker.IsLayerSupported(LayerType::Activation,
+                                                     {inputInfo1, inputInfo2, outputInfo},
+                                                     descriptor,
+                                                     EmptyOptional(),
+                                                     EmptyOptional(),
+                                                     reasonIfNotSupported);
+
+    CHECK(supported);
+}
+
+TEST_CASE("IsLayerSupportedTosaReferenceActivationUnsupported")
+{
+    TensorInfo inputInfo1({1,1,3,4}, DataType::Float32);
+    TensorInfo inputInfo2({1,1,3,4}, DataType::Float32);
+    TensorInfo outputInfo({1,1,3,4}, DataType::Float32);
+
+    TosaRefLayerSupport supportChecker;
+    std::string reasonIfNotSupported;
+    ActivationDescriptor descriptor;
+    descriptor.m_Function = ActivationFunction::HardSwish;
+    auto supported = supportChecker.IsLayerSupported(LayerType::Activation,
+                                                     {inputInfo1, inputInfo2, outputInfo},
+                                                     descriptor,
+                                                     EmptyOptional(),
+                                                     EmptyOptional(),
+                                                     reasonIfNotSupported);
+
+    CHECK(!supported);
+}
+
 TEST_CASE("IsLayerSupportedTosaReferenceMaxPooling2dUnsupported")
 {
     TensorShape inShape = {1,1,3,4};