IVGCVSW-6163 Add Conv3d FrontEnd and Ref Implementation

 * Added front-end
 * Added Reference workload
 * Added Serializer & Deserializer support
 * Added unit tests
 * Added NDHWC DataLayout

Signed-off-by: Matthew Sloyan <matthew.sloyan@arm.com>
Change-Id: Iec4d39e7433b5334d52fa44cf8efc6bcd39319d8
diff --git a/src/armnn/BackendHelper.cpp b/src/armnn/BackendHelper.cpp
index c17d076..1616fd1 100644
--- a/src/armnn/BackendHelper.cpp
+++ b/src/armnn/BackendHelper.cpp
@@ -282,6 +282,21 @@
                                                     reasonIfUnsupported.value());
 }
 
+bool LayerSupportHandle::IsConvolution3dSupported(const TensorInfo& input,
+                                                  const TensorInfo& output,
+                                                  const Convolution3dDescriptor& descriptor,
+                                                  const TensorInfo& weights,
+                                                  const Optional<TensorInfo>& biases,
+                                                  Optional<std::string&> reasonIfUnsupported)
+{
+    return m_LayerSupport->IsConvolution3dSupported(input,
+                                                    output,
+                                                    descriptor,
+                                                    weights,
+                                                    biases,
+                                                    reasonIfUnsupported.value());
+}
+
 bool LayerSupportHandle::IsDebugSupported(const TensorInfo& input,
                                           const TensorInfo& output,
                                           Optional<std::string&> reasonIfUnsupported)
diff --git a/src/armnn/LayersFwd.hpp b/src/armnn/LayersFwd.hpp
index 6f39ca0..49c39b3 100644
--- a/src/armnn/LayersFwd.hpp
+++ b/src/armnn/LayersFwd.hpp
@@ -21,6 +21,7 @@
 #include "layers/ConvertFp32ToBf16Layer.hpp"
 #include "layers/ConvertFp32ToFp16Layer.hpp"
 #include "layers/Convolution2dLayer.hpp"
+#include "layers/Convolution3dLayer.hpp"
 #include "layers/DebugLayer.hpp"
 #include "layers/DepthToSpaceLayer.hpp"
 #include "layers/DepthwiseConvolution2dLayer.hpp"
@@ -119,6 +120,7 @@
 DECLARE_LAYER(ConvertFp32ToBf16)
 DECLARE_LAYER(ConvertFp32ToFp16)
 DECLARE_LAYER(Convolution2d)
+DECLARE_LAYER(Convolution3d)
 DECLARE_LAYER(Debug)
 DECLARE_LAYER(DepthToSpace)
 DECLARE_LAYER(DepthwiseConvolution2d)
diff --git a/src/armnn/Network.cpp b/src/armnn/Network.cpp
index 8409717..4070802 100644
--- a/src/armnn/Network.cpp
+++ b/src/armnn/Network.cpp
@@ -113,6 +113,15 @@
 }
 
 
+IConnectableLayer* INetwork::AddConvolution3dLayer(const Convolution3dDescriptor& convolution3dDescriptor,
+                                                   const ConstTensor& weights,
+                                                   const Optional<ConstTensor>& biases,
+                                                   const char* name)
+{
+    return pNetworkImpl->AddConvolution3dLayer(convolution3dDescriptor, weights, biases, name);
+}
+
+
 IConnectableLayer* INetwork::AddDepthToSpaceLayer(const DepthToSpaceDescriptor& depthToSpaceDescriptor,
                                                   const char* name)
 {
@@ -1991,11 +2000,39 @@
     return AddConvolution2dLayerImpl(convolution2dDescriptor, weights, optionalBiases, name);
 }
 
+IConnectableLayer* NetworkImpl::AddConvolution3dLayer(const Convolution3dDescriptor& convolution3dDescriptor,
+                                                      const ConstTensor& weights,
+                                                      const Optional<ConstTensor>& biases,
+                                                      const char* name)
+{
+    if (convolution3dDescriptor.m_BiasEnabled && !biases.has_value())
+    {
+        throw InvalidArgumentException("AddConvolution2dLayer: biases cannot be empty");
+    }
+
+    const auto layer = m_Graph->AddLayer<Convolution3dLayer>(convolution3dDescriptor, name);
+
+    layer->m_Weight = std::make_shared<ScopedTensorHandle>(weights);
+
+    if (convolution3dDescriptor.m_BiasEnabled)
+    {
+        layer->m_Bias = std::make_shared<ScopedTensorHandle>(biases.value());
+    }
+
+    return layer;
+}
+
+IConnectableLayer* NetworkImpl::AddDepthToSpaceLayer(const DepthToSpaceDescriptor& depthToSpaceDescriptor,
+                                                 const char* name)
+{
+    return m_Graph->AddLayer<DepthToSpaceLayer>(depthToSpaceDescriptor, name);
+}
+
 IConnectableLayer* NetworkImpl::AddDepthwiseConvolution2dLayerImpl(
-    const DepthwiseConvolution2dDescriptor& convolution2dDescriptor,
-    const ConstTensor& weights,
-    const Optional<ConstTensor>& biases,
-    const char* name)
+        const DepthwiseConvolution2dDescriptor& convolution2dDescriptor,
+        const ConstTensor& weights,
+        const Optional<ConstTensor>& biases,
+        const char* name)
 {
     if (convolution2dDescriptor.m_BiasEnabled && !biases.has_value())
     {
@@ -2014,12 +2051,6 @@
     return layer;
 }
 
-IConnectableLayer* NetworkImpl::AddDepthToSpaceLayer(const DepthToSpaceDescriptor& depthToSpaceDescriptor,
-                                                 const char* name)
-{
-    return m_Graph->AddLayer<DepthToSpaceLayer>(depthToSpaceDescriptor, name);
-}
-
 IConnectableLayer* NetworkImpl::AddDepthwiseConvolution2dLayer(
         const DepthwiseConvolution2dDescriptor& convolution2dDescriptor,
         const ConstTensor& weights,
diff --git a/src/armnn/Network.hpp b/src/armnn/Network.hpp
index 67c5b5a..11759c7 100644
--- a/src/armnn/Network.hpp
+++ b/src/armnn/Network.hpp
@@ -89,6 +89,11 @@
                                              const ConstTensor& biases,
                                              const char* name = nullptr);
 
+    IConnectableLayer* AddConvolution3dLayer(const Convolution3dDescriptor& convolution3dDescriptor,
+                                             const ConstTensor& weights,
+                                             const Optional<ConstTensor>& biases,
+                                             const char* name = nullptr);
+
     IConnectableLayer* AddConstantLayer(const ConstTensor& input, const char* name = nullptr);
 
     IConnectableLayer* AddDepthToSpaceLayer(const DepthToSpaceDescriptor& depthToSpaceDescriptor,
diff --git a/src/armnn/SerializeLayerParameters.cpp b/src/armnn/SerializeLayerParameters.cpp
index 73e0cbc..da2c39d 100644
--- a/src/armnn/SerializeLayerParameters.cpp
+++ b/src/armnn/SerializeLayerParameters.cpp
@@ -101,6 +101,33 @@
     fn("DataLayout", GetDataLayoutName(desc.m_DataLayout));
 }
 
+void StringifyLayerParameters<Convolution3dDescriptor>::Serialize(ParameterStringifyFunction& fn,
+                                                                  const Convolution3dDescriptor& desc)
+{
+    {
+        std::stringstream ss;
+        ss << "(" << desc.m_PadTop    << "," << desc.m_PadLeft
+           << "," << desc.m_PadBottom << "," << desc.m_PadRight
+           << "," << desc.m_PadFront  << "," << desc.m_PadBack << ")";
+        fn("Padding(T,L,B,R,F,B)",ss.str());
+    }
+
+    {
+        std::stringstream ss;
+        ss << "(" << desc.m_StrideX <<  "," << desc.m_StrideY << "," << desc.m_StrideZ << ")";
+        fn("Stride(X,Y,Z)", ss.str());
+    }
+
+    {
+        std::stringstream ss;
+        ss << "(" << desc.m_DilationX << "," << desc.m_DilationY << "," << desc.m_DilationZ << ")";
+        fn("Dilation(X,Y)", ss.str());
+    }
+
+    fn("BiasEnabled",(desc.m_BiasEnabled ? "true" : "false"));
+    fn("DataLayout", GetDataLayoutName(desc.m_DataLayout));
+}
+
 void StringifyLayerParameters<DetectionPostProcessDescriptor>::Serialize(ParameterStringifyFunction& fn,
                                                                          const DetectionPostProcessDescriptor& desc)
 {
diff --git a/src/armnn/SerializeLayerParameters.hpp b/src/armnn/SerializeLayerParameters.hpp
index f8fe5e2..8a3630c 100644
--- a/src/armnn/SerializeLayerParameters.hpp
+++ b/src/armnn/SerializeLayerParameters.hpp
@@ -55,6 +55,11 @@
     static void Serialize(ParameterStringifyFunction& fn, const Convolution2dDescriptor& desc);
 };
 
+template <> struct StringifyLayerParameters<Convolution3dDescriptor>
+{
+    static void Serialize(ParameterStringifyFunction& fn, const Convolution3dDescriptor& desc);
+};
+
 template <> struct StringifyLayerParameters<DetectionPostProcessDescriptor>
 {
     static void Serialize(ParameterStringifyFunction& fn, const DetectionPostProcessDescriptor& desc);
diff --git a/src/armnn/layers/Convolution3dLayer.cpp b/src/armnn/layers/Convolution3dLayer.cpp
new file mode 100644
index 0000000..0e38c0b
--- /dev/null
+++ b/src/armnn/layers/Convolution3dLayer.cpp
@@ -0,0 +1,172 @@
+//
+// Copyright © 2021 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "Convolution3dLayer.hpp"
+#include "LayerCloneBase.hpp"
+
+#include <armnnUtils/DataLayoutIndexed.hpp>
+
+#include <backendsCommon/TensorHandle.hpp>
+
+using namespace armnnUtils;
+
+namespace armnn
+{
+
+Convolution3dLayer::Convolution3dLayer(const Convolution3dDescriptor& param, const char* name)
+    : LayerWithParameters(1, 1, LayerType::Convolution3d, param, name)
+{
+}
+
+void Convolution3dLayer::SerializeLayerParameters(ParameterStringifyFunction& fn) const
+{
+    const std::vector<TensorShape>& inputShapes =
+    {
+        GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape(),
+        m_Weight->GetTensorInfo().GetShape()
+    };
+
+    // Conv3d Filter Layout: [D,H,W,I,O]
+    const TensorShape filterShape = inputShapes[1];
+    DataLayoutIndexed dataLayoutIndex(m_Param.m_DataLayout);
+    unsigned int filterDepth = filterShape[0];
+    unsigned int filterHeight = filterShape[1];
+    unsigned int filterWidth = filterShape[2];
+    unsigned int inChannels = filterShape[3];
+    unsigned int outChannels = filterShape[4];
+
+    fn("FilterDepth",std::to_string(filterDepth));
+    fn("FilterHeight",std::to_string(filterHeight));
+    fn("FilterWidth",std::to_string(filterWidth));
+    fn("InputChannels",std::to_string(inChannels));
+    fn("OutputChannels",std::to_string(outChannels));
+
+    LayerWithParameters<Convolution3dDescriptor>::SerializeLayerParameters(fn);
+}
+
+std::unique_ptr<IWorkload> Convolution3dLayer::CreateWorkload(const IWorkloadFactory& factory) const
+{
+    // At this level constant data should not be released.
+    ARMNN_ASSERT_MSG(m_Weight != nullptr, "Convolution3dLayer: Weights data should not be null.");
+
+    Convolution3dQueueDescriptor descriptor;
+    descriptor.m_Weight = m_Weight.get();
+
+    if (m_Param.m_BiasEnabled)
+    {
+        ARMNN_ASSERT_MSG(m_Bias != nullptr, "Convolution3dLayer: Bias data should not be null.");
+        descriptor.m_Bias = m_Bias.get();
+    }
+
+    SetAdditionalInfo(descriptor);
+
+    return factory.CreateConvolution3d(descriptor, PrepInfoAndDesc(descriptor));
+}
+
+Convolution3dLayer* Convolution3dLayer::Clone(Graph& graph) const
+{
+    auto layer = CloneBase<Convolution3dLayer>(graph, m_Param, GetName());
+
+    layer->m_Weight = m_Weight ? m_Weight : nullptr;
+
+    if (layer->m_Param.m_BiasEnabled)
+    {
+        layer->m_Bias = m_Bias ? m_Bias : nullptr;
+    }
+
+    return std::move(layer);
+}
+
+std::vector<TensorShape> Convolution3dLayer::InferOutputShapes(const std::vector<TensorShape>& inputShapes) const
+{
+    ARMNN_ASSERT(inputShapes.size() == 2);
+    const TensorShape& inputShape = inputShapes[0];
+    const TensorShape& filterShape = inputShapes[1];
+
+    ARMNN_ASSERT_MSG(inputShape.GetNumDimensions() == 5, "Convolutions will always have 5D input.");
+
+    ARMNN_ASSERT( m_Param.m_StrideX > 0);
+    ARMNN_ASSERT( m_Param.m_StrideY > 0);
+    ARMNN_ASSERT( m_Param.m_StrideZ > 0);
+
+    DataLayoutIndexed dataLayoutIndex(m_Param.m_DataLayout);
+
+    unsigned int inWidth = inputShape[dataLayoutIndex.GetWidthIndex()];
+    unsigned int inHeight = inputShape[dataLayoutIndex.GetHeightIndex()];
+    unsigned int inDepth = inputShape[dataLayoutIndex.GetDepthIndex()];
+    unsigned int inBatchSize = inputShape[0];
+
+    // Conv3d Filter Layout: [D,H,W,I,O]
+    unsigned int filterDepth = filterShape[0];
+    unsigned int dilatedFilterDepth = filterDepth + (m_Param.m_DilationZ - 1) * (filterDepth - 1);
+    unsigned int readDepth = (inDepth + m_Param.m_PadFront + m_Param.m_PadBack) - dilatedFilterDepth;
+    unsigned int outDepth = 1 + (readDepth / m_Param.m_StrideZ);
+
+    unsigned int filterHeight = filterShape[1];
+    unsigned int dilatedFilterHeight = filterHeight + (m_Param.m_DilationY - 1) * (filterHeight - 1);
+    unsigned int readHeight = (inHeight + m_Param.m_PadTop + m_Param.m_PadBottom) - dilatedFilterHeight;
+    unsigned int outHeight = 1 + (readHeight / m_Param.m_StrideY);
+
+    unsigned int filterWidth = filterShape[2];
+    unsigned int dilatedFilterWidth = filterWidth + (m_Param.m_DilationX - 1) * (filterWidth - 1);
+    unsigned int readWidth = (inWidth + m_Param.m_PadLeft + m_Param.m_PadRight) - dilatedFilterWidth;
+    unsigned int outWidth = 1 + (readWidth / m_Param.m_StrideX);
+
+    unsigned int outChannels = filterShape[4];
+    unsigned int outBatchSize = inBatchSize;
+
+    TensorShape tensorShape = TensorShape( { outBatchSize, outDepth, outHeight, outWidth, outChannels } );
+
+    return std::vector<TensorShape>({ tensorShape });
+}
+
+void Convolution3dLayer::ValidateTensorShapesFromInputs()
+{
+    VerifyLayerConnections(1, CHECK_LOCATION());
+
+    const TensorShape& outputShape = GetOutputSlot(0).GetTensorInfo().GetShape();
+
+    VerifyShapeInferenceType(outputShape, m_ShapeInferenceMethod);
+
+    // check if we m_Weight data is not nullptr
+    ARMNN_ASSERT_MSG(m_Weight != nullptr, "Convolution3dLayer: Weights data should not be null.");
+
+    auto inferredShapes = InferOutputShapes({
+        GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape(),
+        m_Weight->GetTensorInfo().GetShape() });
+
+    ARMNN_ASSERT(inferredShapes.size() == 1);
+
+    ValidateAndCopyShape(outputShape, inferredShapes[0], m_ShapeInferenceMethod, "Convolution3dLayer");
+}
+
+Layer::ConstantTensors Convolution3dLayer::GetConstantTensorsByRef()
+{
+    return {m_Weight, m_Bias};
+}
+
+ARMNN_NO_DEPRECATE_WARN_BEGIN
+void Convolution3dLayer::Accept(ILayerVisitor& visitor) const
+{
+    IgnoreUnused(visitor);
+    throw armnn::Exception("Convolution3dLayer: VisitConvolution3dLayer is not implemented");
+}
+ARMNN_NO_DEPRECATE_WARN_END
+
+void Convolution3dLayer::ExecuteStrategy(IStrategy& strategy) const
+{
+    ManagedConstTensorHandle managedWeight(m_Weight);
+    std::vector<armnn::ConstTensor> constTensors { { managedWeight.GetTensorInfo(), managedWeight.Map() } };
+
+    ManagedConstTensorHandle managedBias(m_Bias);
+    if (GetParameters().m_BiasEnabled)
+    {
+        constTensors.emplace_back(ConstTensor(managedBias.GetTensorInfo(), managedBias.Map()));
+    }
+
+    strategy.ExecuteStrategy(this, GetParameters(), constTensors, GetName());
+}
+
+} // namespace armnn
diff --git a/src/armnn/layers/Convolution3dLayer.hpp b/src/armnn/layers/Convolution3dLayer.hpp
new file mode 100644
index 0000000..bef5715
--- /dev/null
+++ b/src/armnn/layers/Convolution3dLayer.hpp
@@ -0,0 +1,68 @@
+//
+// Copyright © 2021 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include "LayerWithParameters.hpp"
+
+namespace armnn
+{
+
+class ScopedTensorHandle;
+
+/// This layer represents a convolution 3d operation.
+class Convolution3dLayer : public LayerWithParameters<Convolution3dDescriptor>
+{
+public:
+
+    /// A unique pointer to store Weight values.
+    std::shared_ptr<ConstTensorHandle> m_Weight;
+    /// A unique pointer to store Bias values.
+    std::shared_ptr<ConstTensorHandle> m_Bias;
+
+    /// Makes a workload for the Convolution3d type.
+    /// @param [in] graph The graph where this layer can be found.
+    /// @param [in] factory The workload factory which will create the workload.
+    /// @return A pointer to the created workload, or nullptr if not created.
+    virtual std::unique_ptr<IWorkload> CreateWorkload(const IWorkloadFactory& factory) const override;
+
+    /// Creates a dynamically-allocated copy of this layer.
+    /// @param [in] graph The graph into which this layer is being cloned.
+    Convolution3dLayer* Clone(Graph& graph) const override;
+
+    /// Check if the input tensor shape(s)
+    /// will lead to a valid configuration of @ref Convolution3dLayer.
+    /// @param [in] shapeInferenceMethod Indicates if output shape shall be overwritten or just validated.
+    void ValidateTensorShapesFromInputs() override;
+
+    /// By default returns inputShapes if the number of inputs are equal to number of outputs,
+    /// otherwise infers the output shapes from given input shapes and layer properties.
+    /// @param [in] inputShapes The input shapes layer has.
+    /// @return A vector to the inferred output shape.
+    std::vector<TensorShape> InferOutputShapes(const std::vector<TensorShape>& inputShapes) const override;
+
+    ARMNN_NO_DEPRECATE_WARN_BEGIN
+    void Accept(ILayerVisitor& visitor) const override;
+    ARMNN_NO_DEPRECATE_WARN_END
+
+    void ExecuteStrategy(IStrategy& strategy) const override;
+
+    void SerializeLayerParameters(ParameterStringifyFunction& fn) const override;
+
+protected:
+    /// Constructor to create a Convolution3dLayer.
+    /// @param [in] param Convolution3dDescriptor to configure the convolution3d operation.
+    /// @param [in] name Optional name for the layer.
+    Convolution3dLayer(const Convolution3dDescriptor& param, const char* name);
+
+    /// Default destructor
+    ~Convolution3dLayer() = default;
+
+    /// Retrieve the handles to the constant values stored by the layer.
+    /// @return A vector of the constant tensors stored by this layer.
+    ConstantTensors GetConstantTensorsByRef() override;
+};
+
+} // namespace
diff --git a/src/armnn/test/InferOutputTests.cpp b/src/armnn/test/InferOutputTests.cpp
index 81ad7b2..5365b83 100644
--- a/src/armnn/test/InferOutputTests.cpp
+++ b/src/armnn/test/InferOutputTests.cpp
@@ -38,6 +38,9 @@
 // Convolution2D
 ARMNN_SIMPLE_TEST_CASE(Convolution2dInferOutputShape, Convolution2dInferOutputShapeTest)
 
+// Convolution3D
+ARMNN_SIMPLE_TEST_CASE(Convolution3dInferOutputShape, Convolution3dInferOutputShapeTest)
+
 // DepthwiseConvolution2D
 ARMNN_SIMPLE_TEST_CASE(DepthwiseConvolution2dInferOutputShape, DepthwiseConvolution2dInferOutputShapeTest)
 
diff --git a/src/armnn/test/InferOutputTests.hpp b/src/armnn/test/InferOutputTests.hpp
index 6e2676e..e2c8545 100644
--- a/src/armnn/test/InferOutputTests.hpp
+++ b/src/armnn/test/InferOutputTests.hpp
@@ -464,6 +464,43 @@
     CHECK(expectedOutputShape == convolution2dLayer->InferOutputShapes(shapes).at(0));
 }
 
+void Convolution3dInferOutputShapeTest()
+{
+    armnn::Graph graph;
+
+    armnn::Convolution3dDescriptor descriptor;
+    descriptor.m_DilationX = 1;
+    descriptor.m_DilationY = 1;
+    descriptor.m_DilationZ = 1;
+    descriptor.m_PadTop = 1;
+    descriptor.m_PadBottom = 1;
+    descriptor.m_PadLeft = 1;
+    descriptor.m_PadRight = 1;
+    descriptor.m_PadFront = 1;
+    descriptor.m_PadBack = 1;
+    descriptor.m_StrideX = 2;
+    descriptor.m_StrideY = 2;
+    descriptor.m_StrideZ = 2;
+    descriptor.m_DataLayout = armnn::DataLayout::NDHWC;
+
+    armnn::Convolution3dLayer* const convolution3dLayer =
+            graph.AddLayer<armnn::Convolution3dLayer>(descriptor, "convolution3d");
+
+    std::vector<armnn::TensorShape> shapes;
+    const std::vector<unsigned int> inputSize = {1, 5, 5, 5, 1};
+    armnn::TensorShape inputShape(5, inputSize.data());
+    shapes.push_back(inputShape);
+
+    const std::vector<unsigned int> filterSize = {3, 3, 3, 1, 1 };
+    armnn::TensorShape filterShape(5, filterSize.data());
+    shapes.push_back(filterShape);
+
+    const std::vector<unsigned int> expectedOutputSizes = {1, 3, 3, 3, 1};
+    armnn::TensorShape expectedOutputShape(5, expectedOutputSizes.data());
+
+    CHECK(expectedOutputShape == convolution3dLayer->InferOutputShapes(shapes).at(0));
+}
+
 void TransposeConvolution2dInferOutputShapeTest()
 {
     armnn::Graph graph;
diff --git a/src/armnnDeserializer/Deserializer.cpp b/src/armnnDeserializer/Deserializer.cpp
index 1341581..eaeab78 100644
--- a/src/armnnDeserializer/Deserializer.cpp
+++ b/src/armnnDeserializer/Deserializer.cpp
@@ -221,6 +221,7 @@
     m_ParserFunctions[Layer_ConcatLayer]                 = &DeserializerImpl::ParseConcat;
     m_ParserFunctions[Layer_ConstantLayer]               = &DeserializerImpl::ParseConstant;
     m_ParserFunctions[Layer_Convolution2dLayer]          = &DeserializerImpl::ParseConvolution2d;
+    m_ParserFunctions[Layer_Convolution3dLayer]          = &DeserializerImpl::ParseConvolution3d;
     m_ParserFunctions[Layer_DepthToSpaceLayer]           = &DeserializerImpl::ParseDepthToSpace;
     m_ParserFunctions[Layer_DepthwiseConvolution2dLayer] = &DeserializerImpl::ParseDepthwiseConvolution2d;
     m_ParserFunctions[Layer_DequantizeLayer]             = &DeserializerImpl::ParseDequantize;
@@ -304,6 +305,8 @@
             return graphPtr->layers()->Get(layerIndex)->layer_as_ConstantLayer()->base();
         case Layer::Layer_Convolution2dLayer:
             return graphPtr->layers()->Get(layerIndex)->layer_as_Convolution2dLayer()->base();
+        case Layer::Layer_Convolution3dLayer:
+            return graphPtr->layers()->Get(layerIndex)->layer_as_Convolution3dLayer()->base();
         case Layer::Layer_DepthToSpaceLayer:
             return graphPtr->layers()->Get(layerIndex)->layer_as_DepthToSpaceLayer()->base();
         case Layer::Layer_DepthwiseConvolution2dLayer:
@@ -444,6 +447,8 @@
     {
         case armnnSerializer::DataLayout::DataLayout_NHWC:
             return armnn::DataLayout::NHWC;
+        case armnnSerializer::DataLayout::DataLayout_NDHWC:
+            return armnn::DataLayout::NDHWC;
         case armnnSerializer::DataLayout::DataLayout_NCHW:
         default:
             return armnn::DataLayout::NCHW;
@@ -1392,6 +1397,56 @@
     RegisterOutputSlots(graph, layerIndex, layer);
 }
 
+void IDeserializer::DeserializerImpl::ParseConvolution3d(GraphPtr graph, unsigned int layerIndex)
+{
+    CHECK_LAYERS(graph, 0, layerIndex);
+    auto inputs = GetInputs(graph, layerIndex);
+    CHECK_LOCATION();
+    CHECK_VALID_SIZE(inputs.size(), 1);
+
+    auto outputs = GetOutputs(graph, layerIndex);
+    CHECK_VALID_SIZE(outputs.size(), 1);
+
+    auto serializerLayer = graph->layers()->Get(layerIndex)->layer_as_Convolution3dLayer();
+    auto layerName = GetLayerName(graph, layerIndex);
+    auto serializerDescriptor = serializerLayer->descriptor();
+
+    armnn::Convolution3dDescriptor descriptor;
+    descriptor.m_PadLeft = serializerDescriptor->padLeft();
+    descriptor.m_PadRight = serializerDescriptor->padRight();
+    descriptor.m_PadTop = serializerDescriptor->padTop();
+    descriptor.m_PadBottom = serializerDescriptor->padBottom();
+    descriptor.m_PadFront = serializerDescriptor->padFront();
+    descriptor.m_PadBack = serializerDescriptor->padBack();
+    descriptor.m_StrideX = serializerDescriptor->strideX();
+    descriptor.m_StrideY = serializerDescriptor->strideY();
+    descriptor.m_StrideZ = serializerDescriptor->strideZ();
+    descriptor.m_DilationX = serializerDescriptor->dilationX();
+    descriptor.m_DilationY = serializerDescriptor->dilationY();
+    descriptor.m_DilationZ = serializerDescriptor->dilationZ();
+    descriptor.m_BiasEnabled = serializerDescriptor->biasEnabled();;
+    descriptor.m_DataLayout = ToDataLayout(serializerDescriptor->dataLayout());
+
+    armnn::ConstTensor weights = ToConstTensor(serializerLayer->weights());
+    armnn::ConstTensor biases;
+
+    armnn::Optional<armnn::ConstTensor> optionalBiases = armnn::EmptyOptional();
+    if (descriptor.m_BiasEnabled)
+    {
+        biases = ToConstTensor(serializerLayer->biases());
+        optionalBiases = armnn::Optional<armnn::ConstTensor>(biases);
+    }
+    IConnectableLayer* layer = m_Network->AddConvolution3dLayer(descriptor,
+                                                                weights,
+                                                                optionalBiases,
+                                                                layerName.c_str());
+    armnn::TensorInfo outputTensorInfo = ToTensorInfo(outputs[0]);
+    layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
+
+    RegisterInputSlots(graph, layerIndex, layer);
+    RegisterOutputSlots(graph, layerIndex, layer);
+}
+
 void IDeserializer::DeserializerImpl::ParseDepthToSpace(GraphPtr graph, unsigned int layerIndex)
 {
     CHECK_LAYERS(graph, 0, layerIndex);
diff --git a/src/armnnDeserializer/Deserializer.hpp b/src/armnnDeserializer/Deserializer.hpp
index a07e41f..d2291c0 100644
--- a/src/armnnDeserializer/Deserializer.hpp
+++ b/src/armnnDeserializer/Deserializer.hpp
@@ -93,6 +93,7 @@
     void ParseConcat(GraphPtr graph, unsigned int layerIndex);
     void ParseConstant(GraphPtr graph, unsigned int layerIndex);
     void ParseConvolution2d(GraphPtr graph, unsigned int layerIndex);
+    void ParseConvolution3d(GraphPtr graph, unsigned int layerIndex);
     void ParseDepthToSpace(GraphPtr graph, unsigned int layerIndex);
     void ParseDepthwiseConvolution2d(GraphPtr graph, unsigned int layerIndex);
     void ParseDequantize(GraphPtr graph, unsigned int layerIndex);
diff --git a/src/armnnDeserializer/test/DeserializeConvolution3d.cpp b/src/armnnDeserializer/test/DeserializeConvolution3d.cpp
new file mode 100644
index 0000000..057ab6f
--- /dev/null
+++ b/src/armnnDeserializer/test/DeserializeConvolution3d.cpp
@@ -0,0 +1,223 @@
+//
+// Copyright © 2021 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "ParserFlatbuffersSerializeFixture.hpp"
+#include <armnnDeserializer/IDeserializer.hpp>
+
+#include <string>
+
+TEST_SUITE("Deserializer_Convolution3d")
+{
+struct Convolution3dFixture : public ParserFlatbuffersSerializeFixture
+{
+    explicit Convolution3dFixture(const std::string& inputShape,
+                                  const std::string& outputShape,
+                                  const std::string& weightsShape,
+                                  const std::string& dataType)
+    {
+        m_JsonString = R"(
+        {
+          inputIds: [0],
+          outputIds: [2],
+          layers: [
+            {
+              layer_type: "InputLayer",
+              layer: {
+                base: {
+                  layerBindingId: 0,
+                  base: {
+                    layerName: "InputLayer",
+                    layerType: "Input",
+                    inputSlots: [{
+                        index: 0,
+                        connection: {sourceLayerIndex:0, outputSlotIndex:0 },
+                    }],
+                    outputSlots: [
+                      {
+                        index: 0,
+                        tensorInfo: {
+                          dimensions: )" + inputShape + R"(,
+                          dataType: )" + dataType + R"(,
+                          quantizationScale: 0.1,
+                          dimensionSpecificity: [
+                            true,
+                            true,
+                            true,
+                            true,
+                            true
+                          ]
+                        }
+                      }
+                    ]
+                  }
+                }
+              }
+            },
+            {
+              layer_type: "Convolution3dLayer",
+              layer: {
+                base: {
+                  index: 1,
+                  layerName: "convolution3d",
+                  layerType: "Convolution2d",
+                  inputSlots: [
+                    {
+                      index: 0,
+                      connection: {
+                        sourceLayerIndex: 0,
+                        outputSlotIndex: 0
+                      }
+                    }
+                  ],
+                  outputSlots: [
+                    {
+                      index: 0,
+                      tensorInfo: {
+                        dimensions: )" + outputShape + R"(,
+                        dataType: )" + dataType + R"(,
+                        quantizationScale: 0.1,
+                        dimensionSpecificity: [
+                          true,
+                          true,
+                          true,
+                          true,
+                          true
+                        ]
+                      }
+                    }
+                  ]
+                },
+                descriptor: {
+                  strideX: 2,
+                  strideY: 2,
+                  strideZ: 2
+                },
+                weights: {
+                  info: {
+                    dimensions: )" + weightsShape + R"(,
+                    dataType: )" + dataType + R"(,
+                    quantizationScale: 0.1,
+                    dimensionSpecificity: [
+                      true,
+                      true,
+                      true,
+                      true,
+                      true
+                    ]
+                  },
+                  data_type: "ByteData",
+                  data: {
+                    data: [
+                      1, 1, 1,
+                      1, 1, 1,
+                      1, 1, 1,
+
+                      0, 0, 0,
+                      0, 0, 0,
+                      0, 0, 0,
+
+                      0, 0, 0,
+                      0, 0, 0,
+                      0, 0, 0
+                    ]
+                  }
+                }
+              }
+            },
+            {
+              layer_type: "OutputLayer",
+              layer: {
+                base: {
+                  layerBindingId: 2,
+                  base: {
+                    index: 2,
+                    layerName: "OutputLayer",
+                    layerType: "Output",
+                    inputSlots: [
+                      {
+                        connection: {
+                          sourceLayerIndex: 1,
+                          outputSlotIndex: 0
+                        }
+                      }
+                    ],
+                    outputSlots: [{
+                        index: 0,
+                        tensorInfo: {
+                            dimensions: )" + outputShape + R"(,
+                            dataType: )" + dataType + R"(
+                        },
+                    }]
+                  }
+                }
+              }
+            }
+          ],
+          featureVersions: {
+            bindingIdsScheme: 1,
+            weightsLayoutScheme: 1,
+            constantTensorsAsInputs: 1
+          }
+        }
+        )";
+        Setup();
+    }
+
+
+};
+
+struct SimpleConvolution3dFixture : Convolution3dFixture
+{
+    SimpleConvolution3dFixture() : Convolution3dFixture(
+            "[ 1, 5, 5, 5, 1 ]",
+            "[ 1, 2, 2, 2, 1 ]",
+            "[ 3, 3, 3, 1, 1 ]",
+            "QAsymmS8") {}
+};
+
+TEST_CASE_FIXTURE(SimpleConvolution3dFixture, "Convolution3dInt8")
+{
+    RunTest<5, armnn::DataType::QAsymmS8>(
+            0,
+            {{"InputLayer", { 0,  1,  2,  3,  4,
+                              5,  6,  7,  8,  9,
+                             10, 11, 12, 13, 14,
+                             15, 16, 17, 18, 19,
+
+                             20, 21, 22, 23, 24,
+                             25, 26, 27, 28, 29,
+                             30, 31, 32, 33, 34,
+                             35, 36, 37, 38, 39,
+                             40, 41, 42, 43, 44,
+
+                             45, 46, 47, 48, 49,
+                             50, 51, 52, 53, 54,
+                             55, 56, 57, 58, 59,
+                             60, 61, 62, 63, 64,
+                             65, 66, 67, 68, 69,
+
+                             70, 71, 72, 73, 74,
+                             75, 76, 77, 78, 79,
+                             80, 81, 82, 83, 84,
+                             85, 86, 87, 88, 89,
+                             90, 91, 92, 93, 94,
+                             95, 96, 97, 98, 99,
+
+                             100, 101, 102, 103, 104,
+                             105, 106, 107, 108, 109,
+                             110, 111, 112, 113, 114,
+                             115, 116, 117, 118, 119,
+                             120, 121, 122, 123, 124
+                             }}},
+            {{"OutputLayer", {5,  7,
+
+                              14, 16,
+
+                              50, 52,
+
+                              59, 61}}});
+}
+
+}
diff --git a/src/armnnSerializer/ArmnnSchema.fbs b/src/armnnSerializer/ArmnnSchema.fbs
index 740090b..7798288 100644
--- a/src/armnnSerializer/ArmnnSchema.fbs
+++ b/src/armnnSerializer/ArmnnSchema.fbs
@@ -45,7 +45,8 @@
 
 enum DataLayout : byte {
     NHWC = 0,
-    NCHW = 1
+    NCHW = 1,
+    NDHWC = 2
 }
 
 enum ReduceOperation: byte {
@@ -177,6 +178,7 @@
     Shape = 62,
     UnidirectionalSequenceLstm = 63,
     ChannelShuffle = 64,
+    Convolution3d = 65,
 }
 
 // Base layer table to be used as part of other layers
@@ -282,6 +284,30 @@
     dataLayout:DataLayout = NCHW;
 }
 
+table Convolution3dLayer {
+    base:LayerBase;
+    descriptor:Convolution3dDescriptor;
+    weights:ConstTensor;
+    biases:ConstTensor;
+}
+
+table Convolution3dDescriptor {
+    padLeft:uint;
+    padRight:uint;
+    padTop:uint;
+    padBottom:uint;
+    padFront:uint;
+    padBack:uint;
+    strideX:uint;
+    strideY:uint;
+    strideZ:uint;
+    dilationX:uint = 1;
+    dilationY:uint = 1;
+    dilationZ:uint = 1;
+    biasEnabled:bool = false;
+    dataLayout:DataLayout = NDHWC;
+}
+
 table DepthToSpaceLayer {
     base:LayerBase;
     descriptor:DepthToSpaceDescriptor;
@@ -1012,6 +1038,7 @@
     ShapeLayer,
     UnidirectionalSequenceLstmLayer,
     ChannelShuffleLayer,
+    Convolution3dLayer,
 }
 
 table AnyLayer {
diff --git a/src/armnnSerializer/ArmnnSchema_generated.h b/src/armnnSerializer/ArmnnSchema_generated.h
index 653ea6a..8234aa9 100644
--- a/src/armnnSerializer/ArmnnSchema_generated.h
+++ b/src/armnnSerializer/ArmnnSchema_generated.h
@@ -86,6 +86,12 @@
 struct Convolution2dDescriptor;
 struct Convolution2dDescriptorBuilder;
 
+struct Convolution3dLayer;
+struct Convolution3dLayerBuilder;
+
+struct Convolution3dDescriptor;
+struct Convolution3dDescriptorBuilder;
+
 struct DepthToSpaceLayer;
 struct DepthToSpaceLayerBuilder;
 
@@ -533,29 +539,32 @@
 enum DataLayout {
   DataLayout_NHWC = 0,
   DataLayout_NCHW = 1,
+  DataLayout_NDHWC = 2,
   DataLayout_MIN = DataLayout_NHWC,
-  DataLayout_MAX = DataLayout_NCHW
+  DataLayout_MAX = DataLayout_NDHWC
 };
 
-inline const DataLayout (&EnumValuesDataLayout())[2] {
+inline const DataLayout (&EnumValuesDataLayout())[3] {
   static const DataLayout values[] = {
     DataLayout_NHWC,
-    DataLayout_NCHW
+    DataLayout_NCHW,
+    DataLayout_NDHWC
   };
   return values;
 }
 
 inline const char * const *EnumNamesDataLayout() {
-  static const char * const names[3] = {
+  static const char * const names[4] = {
     "NHWC",
     "NCHW",
+    "NDHWC",
     nullptr
   };
   return names;
 }
 
 inline const char *EnumNameDataLayout(DataLayout e) {
-  if (flatbuffers::IsOutRange(e, DataLayout_NHWC, DataLayout_NCHW)) return "";
+  if (flatbuffers::IsOutRange(e, DataLayout_NHWC, DataLayout_NDHWC)) return "";
   const size_t index = static_cast<size_t>(e);
   return EnumNamesDataLayout()[index];
 }
@@ -757,11 +766,12 @@
   LayerType_Shape = 62,
   LayerType_UnidirectionalSequenceLstm = 63,
   LayerType_ChannelShuffle = 64,
+  LayerType_Convolution3d = 65,
   LayerType_MIN = LayerType_Addition,
-  LayerType_MAX = LayerType_ChannelShuffle
+  LayerType_MAX = LayerType_Convolution3d
 };
 
-inline const LayerType (&EnumValuesLayerType())[65] {
+inline const LayerType (&EnumValuesLayerType())[66] {
   static const LayerType values[] = {
     LayerType_Addition,
     LayerType_Input,
@@ -827,13 +837,14 @@
     LayerType_Cast,
     LayerType_Shape,
     LayerType_UnidirectionalSequenceLstm,
-    LayerType_ChannelShuffle
+    LayerType_ChannelShuffle,
+    LayerType_Convolution3d
   };
   return values;
 }
 
 inline const char * const *EnumNamesLayerType() {
-  static const char * const names[66] = {
+  static const char * const names[67] = {
     "Addition",
     "Input",
     "Multiplication",
@@ -899,13 +910,14 @@
     "Shape",
     "UnidirectionalSequenceLstm",
     "ChannelShuffle",
+    "Convolution3d",
     nullptr
   };
   return names;
 }
 
 inline const char *EnumNameLayerType(LayerType e) {
-  if (flatbuffers::IsOutRange(e, LayerType_Addition, LayerType_ChannelShuffle)) return "";
+  if (flatbuffers::IsOutRange(e, LayerType_Addition, LayerType_Convolution3d)) return "";
   const size_t index = static_cast<size_t>(e);
   return EnumNamesLayerType()[index];
 }
@@ -1250,11 +1262,12 @@
   Layer_ShapeLayer = 63,
   Layer_UnidirectionalSequenceLstmLayer = 64,
   Layer_ChannelShuffleLayer = 65,
+  Layer_Convolution3dLayer = 66,
   Layer_MIN = Layer_NONE,
-  Layer_MAX = Layer_ChannelShuffleLayer
+  Layer_MAX = Layer_Convolution3dLayer
 };
 
-inline const Layer (&EnumValuesLayer())[66] {
+inline const Layer (&EnumValuesLayer())[67] {
   static const Layer values[] = {
     Layer_NONE,
     Layer_ActivationLayer,
@@ -1321,13 +1334,14 @@
     Layer_CastLayer,
     Layer_ShapeLayer,
     Layer_UnidirectionalSequenceLstmLayer,
-    Layer_ChannelShuffleLayer
+    Layer_ChannelShuffleLayer,
+    Layer_Convolution3dLayer
   };
   return values;
 }
 
 inline const char * const *EnumNamesLayer() {
-  static const char * const names[67] = {
+  static const char * const names[68] = {
     "NONE",
     "ActivationLayer",
     "AdditionLayer",
@@ -1394,13 +1408,14 @@
     "ShapeLayer",
     "UnidirectionalSequenceLstmLayer",
     "ChannelShuffleLayer",
+    "Convolution3dLayer",
     nullptr
   };
   return names;
 }
 
 inline const char *EnumNameLayer(Layer e) {
-  if (flatbuffers::IsOutRange(e, Layer_NONE, Layer_ChannelShuffleLayer)) return "";
+  if (flatbuffers::IsOutRange(e, Layer_NONE, Layer_Convolution3dLayer)) return "";
   const size_t index = static_cast<size_t>(e);
   return EnumNamesLayer()[index];
 }
@@ -1669,6 +1684,10 @@
   static const Layer enum_value = Layer_ChannelShuffleLayer;
 };
 
+template<> struct LayerTraits<armnnSerializer::Convolution3dLayer> {
+  static const Layer enum_value = Layer_Convolution3dLayer;
+};
+
 bool VerifyLayer(flatbuffers::Verifier &verifier, const void *obj, Layer type);
 bool VerifyLayerVector(flatbuffers::Verifier &verifier, const flatbuffers::Vector<flatbuffers::Offset<void>> *values, const flatbuffers::Vector<uint8_t> *types);
 
@@ -3227,6 +3246,254 @@
   return builder_.Finish();
 }
 
+struct Convolution3dLayer FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  typedef Convolution3dLayerBuilder Builder;
+  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
+    VT_BASE = 4,
+    VT_DESCRIPTOR = 6,
+    VT_WEIGHTS = 8,
+    VT_BIASES = 10
+  };
+  const armnnSerializer::LayerBase *base() const {
+    return GetPointer<const armnnSerializer::LayerBase *>(VT_BASE);
+  }
+  const armnnSerializer::Convolution3dDescriptor *descriptor() const {
+    return GetPointer<const armnnSerializer::Convolution3dDescriptor *>(VT_DESCRIPTOR);
+  }
+  const armnnSerializer::ConstTensor *weights() const {
+    return GetPointer<const armnnSerializer::ConstTensor *>(VT_WEIGHTS);
+  }
+  const armnnSerializer::ConstTensor *biases() const {
+    return GetPointer<const armnnSerializer::ConstTensor *>(VT_BIASES);
+  }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) &&
+           VerifyOffset(verifier, VT_BASE) &&
+           verifier.VerifyTable(base()) &&
+           VerifyOffset(verifier, VT_DESCRIPTOR) &&
+           verifier.VerifyTable(descriptor()) &&
+           VerifyOffset(verifier, VT_WEIGHTS) &&
+           verifier.VerifyTable(weights()) &&
+           VerifyOffset(verifier, VT_BIASES) &&
+           verifier.VerifyTable(biases()) &&
+           verifier.EndTable();
+  }
+};
+
+struct Convolution3dLayerBuilder {
+  typedef Convolution3dLayer Table;
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_base(flatbuffers::Offset<armnnSerializer::LayerBase> base) {
+    fbb_.AddOffset(Convolution3dLayer::VT_BASE, base);
+  }
+  void add_descriptor(flatbuffers::Offset<armnnSerializer::Convolution3dDescriptor> descriptor) {
+    fbb_.AddOffset(Convolution3dLayer::VT_DESCRIPTOR, descriptor);
+  }
+  void add_weights(flatbuffers::Offset<armnnSerializer::ConstTensor> weights) {
+    fbb_.AddOffset(Convolution3dLayer::VT_WEIGHTS, weights);
+  }
+  void add_biases(flatbuffers::Offset<armnnSerializer::ConstTensor> biases) {
+    fbb_.AddOffset(Convolution3dLayer::VT_BIASES, biases);
+  }
+  explicit Convolution3dLayerBuilder(flatbuffers::FlatBufferBuilder &_fbb)
+        : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  Convolution3dLayerBuilder &operator=(const Convolution3dLayerBuilder &);
+  flatbuffers::Offset<Convolution3dLayer> Finish() {
+    const auto end = fbb_.EndTable(start_);
+    auto o = flatbuffers::Offset<Convolution3dLayer>(end);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<Convolution3dLayer> CreateConvolution3dLayer(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    flatbuffers::Offset<armnnSerializer::LayerBase> base = 0,
+    flatbuffers::Offset<armnnSerializer::Convolution3dDescriptor> descriptor = 0,
+    flatbuffers::Offset<armnnSerializer::ConstTensor> weights = 0,
+    flatbuffers::Offset<armnnSerializer::ConstTensor> biases = 0) {
+  Convolution3dLayerBuilder builder_(_fbb);
+  builder_.add_biases(biases);
+  builder_.add_weights(weights);
+  builder_.add_descriptor(descriptor);
+  builder_.add_base(base);
+  return builder_.Finish();
+}
+
+struct Convolution3dDescriptor FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  typedef Convolution3dDescriptorBuilder Builder;
+  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
+    VT_PADLEFT = 4,
+    VT_PADRIGHT = 6,
+    VT_PADTOP = 8,
+    VT_PADBOTTOM = 10,
+    VT_PADFRONT = 12,
+    VT_PADBACK = 14,
+    VT_STRIDEX = 16,
+    VT_STRIDEY = 18,
+    VT_STRIDEZ = 20,
+    VT_DILATIONX = 22,
+    VT_DILATIONY = 24,
+    VT_DILATIONZ = 26,
+    VT_BIASENABLED = 28,
+    VT_DATALAYOUT = 30
+  };
+  uint32_t padLeft() const {
+    return GetField<uint32_t>(VT_PADLEFT, 0);
+  }
+  uint32_t padRight() const {
+    return GetField<uint32_t>(VT_PADRIGHT, 0);
+  }
+  uint32_t padTop() const {
+    return GetField<uint32_t>(VT_PADTOP, 0);
+  }
+  uint32_t padBottom() const {
+    return GetField<uint32_t>(VT_PADBOTTOM, 0);
+  }
+  uint32_t padFront() const {
+    return GetField<uint32_t>(VT_PADFRONT, 0);
+  }
+  uint32_t padBack() const {
+    return GetField<uint32_t>(VT_PADBACK, 0);
+  }
+  uint32_t strideX() const {
+    return GetField<uint32_t>(VT_STRIDEX, 0);
+  }
+  uint32_t strideY() const {
+    return GetField<uint32_t>(VT_STRIDEY, 0);
+  }
+  uint32_t strideZ() const {
+    return GetField<uint32_t>(VT_STRIDEZ, 0);
+  }
+  uint32_t dilationX() const {
+    return GetField<uint32_t>(VT_DILATIONX, 1);
+  }
+  uint32_t dilationY() const {
+    return GetField<uint32_t>(VT_DILATIONY, 1);
+  }
+  uint32_t dilationZ() const {
+    return GetField<uint32_t>(VT_DILATIONZ, 1);
+  }
+  bool biasEnabled() const {
+    return GetField<uint8_t>(VT_BIASENABLED, 0) != 0;
+  }
+  armnnSerializer::DataLayout dataLayout() const {
+    return static_cast<armnnSerializer::DataLayout>(GetField<int8_t>(VT_DATALAYOUT, 2));
+  }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) &&
+           VerifyField<uint32_t>(verifier, VT_PADLEFT) &&
+           VerifyField<uint32_t>(verifier, VT_PADRIGHT) &&
+           VerifyField<uint32_t>(verifier, VT_PADTOP) &&
+           VerifyField<uint32_t>(verifier, VT_PADBOTTOM) &&
+           VerifyField<uint32_t>(verifier, VT_PADFRONT) &&
+           VerifyField<uint32_t>(verifier, VT_PADBACK) &&
+           VerifyField<uint32_t>(verifier, VT_STRIDEX) &&
+           VerifyField<uint32_t>(verifier, VT_STRIDEY) &&
+           VerifyField<uint32_t>(verifier, VT_STRIDEZ) &&
+           VerifyField<uint32_t>(verifier, VT_DILATIONX) &&
+           VerifyField<uint32_t>(verifier, VT_DILATIONY) &&
+           VerifyField<uint32_t>(verifier, VT_DILATIONZ) &&
+           VerifyField<uint8_t>(verifier, VT_BIASENABLED) &&
+           VerifyField<int8_t>(verifier, VT_DATALAYOUT) &&
+           verifier.EndTable();
+  }
+};
+
+struct Convolution3dDescriptorBuilder {
+  typedef Convolution3dDescriptor Table;
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_padLeft(uint32_t padLeft) {
+    fbb_.AddElement<uint32_t>(Convolution3dDescriptor::VT_PADLEFT, padLeft, 0);
+  }
+  void add_padRight(uint32_t padRight) {
+    fbb_.AddElement<uint32_t>(Convolution3dDescriptor::VT_PADRIGHT, padRight, 0);
+  }
+  void add_padTop(uint32_t padTop) {
+    fbb_.AddElement<uint32_t>(Convolution3dDescriptor::VT_PADTOP, padTop, 0);
+  }
+  void add_padBottom(uint32_t padBottom) {
+    fbb_.AddElement<uint32_t>(Convolution3dDescriptor::VT_PADBOTTOM, padBottom, 0);
+  }
+  void add_padFront(uint32_t padFront) {
+    fbb_.AddElement<uint32_t>(Convolution3dDescriptor::VT_PADFRONT, padFront, 0);
+  }
+  void add_padBack(uint32_t padBack) {
+    fbb_.AddElement<uint32_t>(Convolution3dDescriptor::VT_PADBACK, padBack, 0);
+  }
+  void add_strideX(uint32_t strideX) {
+    fbb_.AddElement<uint32_t>(Convolution3dDescriptor::VT_STRIDEX, strideX, 0);
+  }
+  void add_strideY(uint32_t strideY) {
+    fbb_.AddElement<uint32_t>(Convolution3dDescriptor::VT_STRIDEY, strideY, 0);
+  }
+  void add_strideZ(uint32_t strideZ) {
+    fbb_.AddElement<uint32_t>(Convolution3dDescriptor::VT_STRIDEZ, strideZ, 0);
+  }
+  void add_dilationX(uint32_t dilationX) {
+    fbb_.AddElement<uint32_t>(Convolution3dDescriptor::VT_DILATIONX, dilationX, 1);
+  }
+  void add_dilationY(uint32_t dilationY) {
+    fbb_.AddElement<uint32_t>(Convolution3dDescriptor::VT_DILATIONY, dilationY, 1);
+  }
+  void add_dilationZ(uint32_t dilationZ) {
+    fbb_.AddElement<uint32_t>(Convolution3dDescriptor::VT_DILATIONZ, dilationZ, 1);
+  }
+  void add_biasEnabled(bool biasEnabled) {
+    fbb_.AddElement<uint8_t>(Convolution3dDescriptor::VT_BIASENABLED, static_cast<uint8_t>(biasEnabled), 0);
+  }
+  void add_dataLayout(armnnSerializer::DataLayout dataLayout) {
+    fbb_.AddElement<int8_t>(Convolution3dDescriptor::VT_DATALAYOUT, static_cast<int8_t>(dataLayout), 2);
+  }
+  explicit Convolution3dDescriptorBuilder(flatbuffers::FlatBufferBuilder &_fbb)
+        : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  Convolution3dDescriptorBuilder &operator=(const Convolution3dDescriptorBuilder &);
+  flatbuffers::Offset<Convolution3dDescriptor> Finish() {
+    const auto end = fbb_.EndTable(start_);
+    auto o = flatbuffers::Offset<Convolution3dDescriptor>(end);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<Convolution3dDescriptor> CreateConvolution3dDescriptor(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    uint32_t padLeft = 0,
+    uint32_t padRight = 0,
+    uint32_t padTop = 0,
+    uint32_t padBottom = 0,
+    uint32_t padFront = 0,
+    uint32_t padBack = 0,
+    uint32_t strideX = 0,
+    uint32_t strideY = 0,
+    uint32_t strideZ = 0,
+    uint32_t dilationX = 1,
+    uint32_t dilationY = 1,
+    uint32_t dilationZ = 1,
+    bool biasEnabled = false,
+    armnnSerializer::DataLayout dataLayout = armnnSerializer::DataLayout_NDHWC) {
+  Convolution3dDescriptorBuilder builder_(_fbb);
+  builder_.add_dilationZ(dilationZ);
+  builder_.add_dilationY(dilationY);
+  builder_.add_dilationX(dilationX);
+  builder_.add_strideZ(strideZ);
+  builder_.add_strideY(strideY);
+  builder_.add_strideX(strideX);
+  builder_.add_padBack(padBack);
+  builder_.add_padFront(padFront);
+  builder_.add_padBottom(padBottom);
+  builder_.add_padTop(padTop);
+  builder_.add_padRight(padRight);
+  builder_.add_padLeft(padLeft);
+  builder_.add_dataLayout(dataLayout);
+  builder_.add_biasEnabled(biasEnabled);
+  return builder_.Finish();
+}
+
 struct DepthToSpaceLayer FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
   typedef DepthToSpaceLayerBuilder Builder;
   enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
@@ -9963,6 +10230,9 @@
   const armnnSerializer::ChannelShuffleLayer *layer_as_ChannelShuffleLayer() const {
     return layer_type() == armnnSerializer::Layer_ChannelShuffleLayer ? static_cast<const armnnSerializer::ChannelShuffleLayer *>(layer()) : nullptr;
   }
+  const armnnSerializer::Convolution3dLayer *layer_as_Convolution3dLayer() const {
+    return layer_type() == armnnSerializer::Layer_Convolution3dLayer ? static_cast<const armnnSerializer::Convolution3dLayer *>(layer()) : nullptr;
+  }
   bool Verify(flatbuffers::Verifier &verifier) const {
     return VerifyTableStart(verifier) &&
            VerifyField<uint8_t>(verifier, VT_LAYER_TYPE) &&
@@ -10232,6 +10502,10 @@
   return layer_as_ChannelShuffleLayer();
 }
 
+template<> inline const armnnSerializer::Convolution3dLayer *AnyLayer::layer_as<armnnSerializer::Convolution3dLayer>() const {
+  return layer_as_Convolution3dLayer();
+}
+
 struct AnyLayerBuilder {
   typedef AnyLayer Table;
   flatbuffers::FlatBufferBuilder &fbb_;
@@ -10722,6 +10996,10 @@
       auto ptr = reinterpret_cast<const armnnSerializer::ChannelShuffleLayer *>(obj);
       return verifier.VerifyTable(ptr);
     }
+    case Layer_Convolution3dLayer: {
+      auto ptr = reinterpret_cast<const armnnSerializer::Convolution3dLayer *>(obj);
+      return verifier.VerifyTable(ptr);
+    }
     default: return true;
   }
 }
diff --git a/src/armnnSerializer/Serializer.cpp b/src/armnnSerializer/Serializer.cpp
index 9a3a270..efaf9f8 100644
--- a/src/armnnSerializer/Serializer.cpp
+++ b/src/armnnSerializer/Serializer.cpp
@@ -397,6 +397,54 @@
     CreateAnyLayer(flatBufferLayer.o, serializer::Layer::Layer_Convolution2dLayer);
 }
 
+// Build FlatBuffer for Convolution2dLayer
+void SerializerStrategy::SerializeConvolution3dLayer(const armnn::IConnectableLayer* layer,
+                                                     const armnn::Convolution3dDescriptor& descriptor,
+                                                     const std::vector<armnn::ConstTensor>& constants,
+                                                     const char* name)
+{
+    IgnoreUnused(name);
+
+    const armnn::ConstTensor weights = constants[0];
+
+    // Create FlatBuffer BaseLayer
+    auto flatBufferBaseLayer = CreateLayerBase(layer, serializer::LayerType::LayerType_Convolution2d);
+
+    auto flatBufferDescriptor = CreateConvolution3dDescriptor(m_flatBufferBuilder,
+                                                              descriptor.m_PadLeft,
+                                                              descriptor.m_PadRight,
+                                                              descriptor.m_PadTop,
+                                                              descriptor.m_PadBottom,
+                                                              descriptor.m_PadFront,
+                                                              descriptor.m_PadBack,
+                                                              descriptor.m_StrideX,
+                                                              descriptor.m_StrideY,
+                                                              descriptor.m_StrideZ,
+                                                              descriptor.m_DilationX,
+                                                              descriptor.m_DilationY,
+                                                              descriptor.m_DilationZ,
+                                                              descriptor.m_BiasEnabled,
+                                                              GetFlatBufferDataLayout(descriptor.m_DataLayout));
+    auto flatBufferWeightsConstTensorInfo = CreateConstTensorInfo(weights);
+    flatbuffers::Offset<serializer::ConstTensor> flatBufferBiasesConstTensorInfo;
+
+    if (constants.size() > 1)
+    {
+        const armnn::ConstTensor biases = constants[1];
+        flatBufferBiasesConstTensorInfo = CreateConstTensorInfo(biases);
+    }
+
+    // Create the FlatBuffer Convolution2dLayer
+    auto flatBufferLayer = CreateConvolution3dLayer(m_flatBufferBuilder,
+                                                    flatBufferBaseLayer,
+                                                    flatBufferDescriptor,
+                                                    flatBufferWeightsConstTensorInfo,
+                                                    flatBufferBiasesConstTensorInfo);
+
+    // Add the AnyLayer to the FlatBufferLayers
+    CreateAnyLayer(flatBufferLayer.o, serializer::Layer::Layer_Convolution3dLayer);
+}
+
 void SerializerStrategy::SerializeDepthToSpaceLayer(const armnn::IConnectableLayer* layer,
                                                const armnn::DepthToSpaceDescriptor& descriptor,
                                                const char* name)
@@ -2054,6 +2102,16 @@
                                         name);
             break;
         }
+        case armnn::LayerType::Convolution3d :
+        {
+            const armnn::Convolution3dDescriptor& layerDescriptor =
+                    static_cast<const armnn::Convolution3dDescriptor&>(descriptor);
+            SerializeConvolution3dLayer(layer,
+                                        layerDescriptor,
+                                        constants,
+                                        name);
+            break;
+        }
         case armnn::LayerType::DepthToSpace :
         {
             const armnn::DepthToSpaceDescriptor& layerDescriptor =
diff --git a/src/armnnSerializer/Serializer.hpp b/src/armnnSerializer/Serializer.hpp
index 43fb0f4..1161095 100644
--- a/src/armnnSerializer/Serializer.hpp
+++ b/src/armnnSerializer/Serializer.hpp
@@ -144,12 +144,17 @@
                               const char* name = nullptr);
 
     void SerializeConstantLayer(const armnn::IConnectableLayer* layer,
-                                const std::vector<armnn::ConstTensor>& contants,
+                                const std::vector<armnn::ConstTensor>& constants,
                                 const char* name = nullptr);
 
     void SerializeConvolution2dLayer(const armnn::IConnectableLayer* layer,
                                      const armnn::Convolution2dDescriptor& descriptor,
-                                     const std::vector<armnn::ConstTensor>& contants,
+                                     const std::vector<armnn::ConstTensor>& constants,
+                                     const char* name = nullptr);
+
+    void SerializeConvolution3dLayer(const armnn::IConnectableLayer* layer,
+                                     const armnn::Convolution3dDescriptor& descriptor,
+                                     const std::vector<armnn::ConstTensor>& constants,
                                      const char* name = nullptr);
 
     void SerializeDepthToSpaceLayer(const armnn::IConnectableLayer* layer,
diff --git a/src/armnnSerializer/SerializerUtils.cpp b/src/armnnSerializer/SerializerUtils.cpp
index 85ce01d..fca6db8 100644
--- a/src/armnnSerializer/SerializerUtils.cpp
+++ b/src/armnnSerializer/SerializerUtils.cpp
@@ -97,6 +97,8 @@
     {
         case armnn::DataLayout::NHWC:
             return armnnSerializer::DataLayout::DataLayout_NHWC;
+        case armnn::DataLayout::NDHWC:
+            return armnnSerializer::DataLayout::DataLayout_NDHWC;
         case armnn::DataLayout::NCHW:
         default:
             return armnnSerializer::DataLayout::DataLayout_NCHW;
diff --git a/src/armnnSerializer/test/SerializerTests.cpp b/src/armnnSerializer/test/SerializerTests.cpp
index cd7fd5c..2f8fd73 100644
--- a/src/armnnSerializer/test/SerializerTests.cpp
+++ b/src/armnnSerializer/test/SerializerTests.cpp
@@ -439,6 +439,61 @@
     deserializedNetwork->ExecuteStrategy(verifier);
 }
 
+TEST_CASE("SerializeConvolution3d")
+{
+    const std::string layerName("convolution3d");
+    const armnn::TensorInfo inputInfo ({ 1, 5, 5, 5, 1 }, armnn::DataType::Float32);
+    const armnn::TensorInfo outputInfo({ 1, 2, 2, 2, 1 }, armnn::DataType::Float32);
+
+    const armnn::TensorInfo weightsInfo({ 3, 3, 3, 1, 1 }, armnn::DataType::Float32);
+    const armnn::TensorInfo biasesInfo ({ 1 }, armnn::DataType::Float32);
+
+    std::vector<float> weightsData = GenerateRandomData<float>(weightsInfo.GetNumElements());
+    armnn::ConstTensor weights(weightsInfo, weightsData);
+
+    std::vector<float> biasesData = GenerateRandomData<float>(biasesInfo.GetNumElements());
+    armnn::ConstTensor biases(biasesInfo, biasesData);
+
+    armnn::Convolution3dDescriptor descriptor;
+    descriptor.m_PadLeft     = 0;
+    descriptor.m_PadRight    = 0;
+    descriptor.m_PadTop      = 0;
+    descriptor.m_PadBottom   = 0;
+    descriptor.m_PadFront    = 0;
+    descriptor.m_PadBack     = 0;
+    descriptor.m_DilationX   = 1;
+    descriptor.m_DilationY   = 1;
+    descriptor.m_DilationZ   = 1;
+    descriptor.m_StrideX     = 2;
+    descriptor.m_StrideY     = 2;
+    descriptor.m_StrideZ     = 2;
+    descriptor.m_BiasEnabled = true;
+    descriptor.m_DataLayout  = armnn::DataLayout::NDHWC;
+
+    armnn::INetworkPtr network = armnn::INetwork::Create();
+    armnn::IConnectableLayer* const inputLayer  = network->AddInputLayer(0);
+    armnn::IConnectableLayer* const convLayer   =
+            network->AddConvolution3dLayer(descriptor,
+                                           weights,
+                                           armnn::Optional<armnn::ConstTensor>(biases),
+                                           layerName.c_str());
+    armnn::IConnectableLayer* const outputLayer = network->AddOutputLayer(0);
+
+    inputLayer->GetOutputSlot(0).Connect(convLayer->GetInputSlot(0));
+    convLayer->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0));
+
+    inputLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
+    convLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
+
+    armnn::INetworkPtr deserializedNetwork = DeserializeNetwork(SerializeNetwork(*network));
+    CHECK(deserializedNetwork);
+
+    const std::vector<armnn::ConstTensor>& constants {weights, biases};
+    LayerVerifierBaseWithDescriptorAndConstants<armnn::Convolution3dDescriptor> verifier(
+            layerName, {inputInfo}, {outputInfo}, descriptor, constants);
+    deserializedNetwork->ExecuteStrategy(verifier);
+}
+
 TEST_CASE("SerializeDepthToSpace")
 {
     const std::string layerName("depthToSpace");
diff --git a/src/armnnUtils/DataLayoutIndexed.cpp b/src/armnnUtils/DataLayoutIndexed.cpp
index 18c005a..c1c98fc 100644
--- a/src/armnnUtils/DataLayoutIndexed.cpp
+++ b/src/armnnUtils/DataLayoutIndexed.cpp
@@ -25,6 +25,12 @@
             m_HeightIndex   = 2;
             m_WidthIndex    = 3;
             break;
+        case armnn::DataLayout::NDHWC:
+            m_DepthIndex    = 1;
+            m_HeightIndex   = 2;
+            m_WidthIndex    = 3;
+            m_ChannelsIndex = 4;
+            break;
         default:
             throw armnn::InvalidArgumentException("Unknown DataLayout value: " +
                                                   std::to_string(static_cast<int>(dataLayout)));
diff --git a/src/backends/backendsCommon/LayerSupportBase.cpp b/src/backends/backendsCommon/LayerSupportBase.cpp
index 2753c92..2c3f827 100644
--- a/src/backends/backendsCommon/LayerSupportBase.cpp
+++ b/src/backends/backendsCommon/LayerSupportBase.cpp
@@ -165,6 +165,16 @@
     return DefaultLayerSupport(__func__, __FILE__, __LINE__, reasonIfUnsupported);
 }
 
+bool LayerSupportBase::IsConvolution3dSupported(const TensorInfo&, // input
+                                                const TensorInfo&, // output
+                                                const Convolution3dDescriptor&, // descriptor
+                                                const TensorInfo&, // weights
+                                                const Optional<TensorInfo>&, // biases
+                                                Optional<std::string&> reasonIfUnsupported) const
+{
+    return DefaultLayerSupport(__func__, __FILE__, __LINE__, reasonIfUnsupported);
+}
+
 bool LayerSupportBase::IsDebugSupported(const TensorInfo&, // input
                                         const TensorInfo&, // output
                                         Optional<std::string&> reasonIfUnsupported) const
diff --git a/src/backends/backendsCommon/LayerSupportBase.hpp b/src/backends/backendsCommon/LayerSupportBase.hpp
index cc68a22..240b1da 100644
--- a/src/backends/backendsCommon/LayerSupportBase.hpp
+++ b/src/backends/backendsCommon/LayerSupportBase.hpp
@@ -94,6 +94,13 @@
                                   const Optional<TensorInfo>& biases,
                                   Optional<std::string&> reasonIfUnsupported = EmptyOptional()) const override;
 
+    bool IsConvolution3dSupported(const TensorInfo& input,
+                                  const TensorInfo& output,
+                                  const Convolution3dDescriptor& descriptor,
+                                  const TensorInfo& weights,
+                                  const Optional<TensorInfo>& biases,
+                                  Optional<std::string&> reasonIfUnsupported = EmptyOptional()) const override;
+
     bool IsDebugSupported(const TensorInfo& input,
                           const TensorInfo& output,
                           Optional<std::string&> reasonIfUnsupported = EmptyOptional()) const override;
diff --git a/src/backends/backendsCommon/WorkloadData.cpp b/src/backends/backendsCommon/WorkloadData.cpp
index a6def84..fe22133 100644
--- a/src/backends/backendsCommon/WorkloadData.cpp
+++ b/src/backends/backendsCommon/WorkloadData.cpp
@@ -1322,6 +1322,67 @@
     }
 }
 
+void Convolution3dQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
+{
+    const std::string descriptorName{"Convolution3dQueueDescriptor"};
+
+    ValidateNumInputs(workloadInfo,  descriptorName, 1);
+    ValidateNumOutputs(workloadInfo, descriptorName, 1);
+
+    const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
+    const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
+
+    ValidateTensorNumDimensions(inputTensorInfo,  descriptorName, 5, "input");
+    ValidateTensorNumDimensions(outputTensorInfo, descriptorName, 5, "output");
+
+    ValidatePointer(m_Weight, descriptorName, "weight");
+
+    const TensorInfo& weightTensorInfo = m_Weight->GetTensorInfo();
+    ValidateTensorNumDimensions(weightTensorInfo, descriptorName, 5, "weight");
+
+    ValidateWeightDataType(inputTensorInfo, weightTensorInfo, descriptorName);
+
+    Optional<TensorInfo> optionalBiasTensorInfo;
+    if (m_Parameters.m_BiasEnabled)
+    {
+        ValidatePointer(m_Bias, descriptorName, "bias");
+
+        optionalBiasTensorInfo = MakeOptional<TensorInfo>(m_Bias->GetTensorInfo());
+        const TensorInfo& biasTensorInfo = optionalBiasTensorInfo.value();
+
+        ValidateTensorDataType(biasTensorInfo, GetBiasDataType(inputTensorInfo.GetDataType()), descriptorName, "bias");
+        ValidateBiasTensorQuantization(biasTensorInfo, inputTensorInfo, weightTensorInfo, descriptorName);
+    }
+
+    if (m_Parameters.m_StrideX <= 0 || m_Parameters.m_StrideY <= 0 || m_Parameters.m_StrideZ <= 0 )
+    {
+        throw InvalidArgumentException(
+                fmt::format("{}: strideX (provided {}), strideY (provided {}) or strideZ (provided {})"
+                            "cannot be either negative or 0.",
+                            descriptorName, m_Parameters.m_StrideX, m_Parameters.m_StrideY, m_Parameters.m_StrideZ));
+    }
+
+    ValidatePerAxisQuantization(inputTensorInfo,
+                                outputTensorInfo,
+                                weightTensorInfo,
+                                optionalBiasTensorInfo,
+                                descriptorName);
+
+    std::vector<DataType> supportedTypes =
+    {
+        DataType::BFloat16,
+        DataType::Float16,
+        DataType::Float32,
+        DataType::QAsymmS8,
+        DataType::QAsymmU8,
+        DataType::QSymmS16,
+        DataType::QSymmS8
+    };
+
+    ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
+    ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
+}
+
 void DepthwiseConvolution2dQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
 {
     const std::string descriptorName{"DepthwiseConvolution2dQueueDescriptor"};
diff --git a/src/backends/backendsCommon/WorkloadData.hpp b/src/backends/backendsCommon/WorkloadData.hpp
index b90c29c..896081e 100644
--- a/src/backends/backendsCommon/WorkloadData.hpp
+++ b/src/backends/backendsCommon/WorkloadData.hpp
@@ -208,6 +208,21 @@
     void Validate(const WorkloadInfo& workloadInfo) const;
 };
 
+// Convolution 2D layer workload data.
+struct Convolution3dQueueDescriptor : QueueDescriptorWithParameters<Convolution3dDescriptor>
+{
+    Convolution3dQueueDescriptor()
+        : m_Weight(nullptr)
+        , m_Bias(nullptr)
+    {
+    }
+
+    const ConstTensorHandle* m_Weight;
+    const ConstTensorHandle* m_Bias;
+
+    void Validate(const WorkloadInfo& workloadInfo) const;
+};
+
 /// Depthwise Convolution 2D layer workload data.
 ///
 /// @note
diff --git a/src/backends/backendsCommon/WorkloadFactory.cpp b/src/backends/backendsCommon/WorkloadFactory.cpp
index 00263ec..666f83d 100644
--- a/src/backends/backendsCommon/WorkloadFactory.cpp
+++ b/src/backends/backendsCommon/WorkloadFactory.cpp
@@ -225,14 +225,13 @@
             const TensorInfo output = OverrideDataType(layer.GetOutputSlot(0).GetTensorInfo(), dataType);
             ARMNN_ASSERT(cLayer->m_Weight.get() != nullptr);
 
-            const Convolution2dDescriptor& descriptor  = cLayer->GetParameters();
+            const Convolution2dDescriptor& descriptor = cLayer->GetParameters();
 
             // Construct optional biases object based on the value of m_BiasEnabled
             Optional<TensorInfo> biases;
             if (descriptor.m_BiasEnabled)
             {
-                biases =
-                    OverrideDataType(cLayer->m_Bias->GetTensorInfo(), GetBiasTypeFromWeightsType(dataType));
+                biases = OverrideDataType(cLayer->m_Bias->GetTensorInfo(), GetBiasTypeFromWeightsType(dataType));
             }
 
             result = layerSupportObject.IsConvolution2dSupported(
@@ -244,6 +243,33 @@
                                               reason);
             break;
         }
+        case LayerType::Convolution3d:
+        {
+            auto cLayer = PolymorphicDowncast<const Convolution3dLayer*>(&layer);
+
+            const TensorInfo input  = OverrideDataType(layer.GetInputSlot(0).GetConnection()->GetTensorInfo(),
+                                                       dataType);
+            const TensorInfo output = OverrideDataType(layer.GetOutputSlot(0).GetTensorInfo(), dataType);
+            ARMNN_ASSERT(cLayer->m_Weight.get() != nullptr);
+
+            const Convolution3dDescriptor& descriptor = cLayer->GetParameters();
+
+            // Construct optional biases object based on the value of m_BiasEnabled
+            Optional<TensorInfo> biases;
+            if (descriptor.m_BiasEnabled)
+            {
+                biases = OverrideDataType(cLayer->m_Bias->GetTensorInfo(), GetBiasTypeFromWeightsType(dataType));
+            }
+
+            result = layerSupportObject.IsConvolution3dSupported(
+                                              input,
+                                              output,
+                                              descriptor,
+                                              OverrideDataType(cLayer->m_Weight->GetTensorInfo(), dataType),
+                                              biases,
+                                              reason);
+            break;
+        }
         case LayerType::Debug:
         {
             const TensorInfo& input = layer.GetInputSlot(0).GetConnection()->GetTensorInfo();
@@ -1570,6 +1596,12 @@
     return std::unique_ptr<IWorkload>();
 }
 
+std::unique_ptr<IWorkload> IWorkloadFactory::CreateConvolution3d(const Convolution3dQueueDescriptor& /*descriptor*/,
+                                                                 const WorkloadInfo& /*info*/) const
+{
+    return std::unique_ptr<IWorkload>();
+}
+
 std::unique_ptr<IWorkload> IWorkloadFactory::CreateDebug(const DebugQueueDescriptor& /*descriptor*/,
                                                          const WorkloadInfo& /*info*/) const
 {
diff --git a/src/backends/backendsCommon/WorkloadFactory.hpp b/src/backends/backendsCommon/WorkloadFactory.hpp
index e84657e..c16fcb8 100644
--- a/src/backends/backendsCommon/WorkloadFactory.hpp
+++ b/src/backends/backendsCommon/WorkloadFactory.hpp
@@ -115,7 +115,10 @@
                                                                const WorkloadInfo& info) const;
 
     virtual std::unique_ptr<IWorkload> CreateConvolution2d(const Convolution2dQueueDescriptor& descriptor,
-                                                           const WorkloadInfo&               info) const;
+                                                           const WorkloadInfo& info) const;
+
+    virtual std::unique_ptr<IWorkload> CreateConvolution3d(const Convolution3dQueueDescriptor& descriptor,
+                                                           const WorkloadInfo& info) const;
 
     virtual std::unique_ptr<IWorkload> CreateDebug(const DebugQueueDescriptor& descriptor,
                                                    const WorkloadInfo& info) const;
diff --git a/src/backends/backendsCommon/common.mk b/src/backends/backendsCommon/common.mk
index 7d3558c..8c1037a 100644
--- a/src/backends/backendsCommon/common.mk
+++ b/src/backends/backendsCommon/common.mk
@@ -50,6 +50,7 @@
     test/layerTests/ConcatTestImpl.cpp \
     test/layerTests/ConstantTestImpl.cpp \
     test/layerTests/Conv2dTestImpl.cpp \
+    test/layerTests/Conv3dTestImpl.cpp \
     test/layerTests/ConvertBf16ToFp32TestImpl.cpp \
     test/layerTests/ConvertFp16ToFp32TestImpl.cpp \
     test/layerTests/ConvertFp32ToBf16TestImpl.cpp \
diff --git a/src/backends/backendsCommon/test/CMakeLists.txt b/src/backends/backendsCommon/test/CMakeLists.txt
index c17ab0f..ea33513 100644
--- a/src/backends/backendsCommon/test/CMakeLists.txt
+++ b/src/backends/backendsCommon/test/CMakeLists.txt
@@ -75,6 +75,8 @@
     layerTests/ConstantTestImpl.hpp
     layerTests/Conv2dTestImpl.cpp
     layerTests/Conv2dTestImpl.hpp
+    layerTests/Conv3dTestImpl.cpp
+    layerTests/Conv3dTestImpl.hpp
     layerTests/ConvertBf16ToFp32TestImpl.cpp
     layerTests/ConvertBf16ToFp32TestImpl.hpp
     layerTests/ConvertFp16ToFp32TestImpl.cpp
diff --git a/src/backends/backendsCommon/test/IsLayerSupportedTestImpl.hpp b/src/backends/backendsCommon/test/IsLayerSupportedTestImpl.hpp
index c2d2184..76312ce 100644
--- a/src/backends/backendsCommon/test/IsLayerSupportedTestImpl.hpp
+++ b/src/backends/backendsCommon/test/IsLayerSupportedTestImpl.hpp
@@ -638,6 +638,8 @@
 
 DECLARE_LAYER_POLICY_2_PARAM(Convolution2d)
 
+DECLARE_LAYER_POLICY_2_PARAM(Convolution3d)
+
 DECLARE_LAYER_POLICY_1_PARAM(MemCopy)
 
 DECLARE_LAYER_POLICY_1_PARAM(MemImport)
diff --git a/src/backends/backendsCommon/test/LayerTests.hpp b/src/backends/backendsCommon/test/LayerTests.hpp
index 9f1fa88..0dcd3d1 100644
--- a/src/backends/backendsCommon/test/LayerTests.hpp
+++ b/src/backends/backendsCommon/test/LayerTests.hpp
@@ -20,6 +20,7 @@
 #include <backendsCommon/test/layerTests/ConvertFp32ToBf16TestImpl.hpp>
 #include <backendsCommon/test/layerTests/ConvertFp32ToFp16TestImpl.hpp>
 #include <backendsCommon/test/layerTests/Conv2dTestImpl.hpp>
+#include <backendsCommon/test/layerTests/Conv3dTestImpl.hpp>
 #include <backendsCommon/test/layerTests/ConstantTestImpl.hpp>
 #include <backendsCommon/test/layerTests/DebugTestImpl.hpp>
 #include <backendsCommon/test/layerTests/DepthToSpaceTestImpl.hpp>
diff --git a/src/backends/backendsCommon/test/layerTests/Conv3dTestImpl.cpp b/src/backends/backendsCommon/test/layerTests/Conv3dTestImpl.cpp
new file mode 100644
index 0000000..2f02189
--- /dev/null
+++ b/src/backends/backendsCommon/test/layerTests/Conv3dTestImpl.cpp
@@ -0,0 +1,1038 @@
+//
+// Copyright © 2021 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "Conv3dTestImpl.hpp"
+
+#include <QuantizeHelper.hpp>
+
+#include <armnnUtils/DataLayoutIndexed.hpp>
+
+#include <backendsCommon/TensorHandle.hpp>
+
+#include <backendsCommon/test/TensorCopyUtils.hpp>
+#include <backendsCommon/test/WorkloadTestUtils.hpp>
+
+#include <test/TensorHelpers.hpp>
+
+using namespace armnnUtils;
+
+//
+// Helper templates
+//
+
+// Helper template that returns a quantized bias depending on the number of output channels.
+template<armnn::DataType ArmnnType, typename T = armnn::ResolveType<ArmnnType>>
+std::vector<T> GetBiasData(bool biasEnabled, float qScale, armnn::TensorInfo outputInfo, armnn::DataLayout layout)
+{
+    if(!biasEnabled)
+    {
+        return std::vector<T>();
+    }
+    else
+    {
+        const armnnUtils::DataLayoutIndexed dataLayoutIndexed(layout);
+        const unsigned int outputChannels = outputInfo.GetShape()[dataLayoutIndexed.GetChannelsIndex()];
+
+        switch (outputChannels)
+        {
+            case 1:
+            {
+                return QuantizedVector<T>({2}, qScale, 0);
+            }
+            case 2:
+            default:
+            {
+                return QuantizedVector<T>({0, 2}, qScale, 0);
+            }
+        }
+    }
+}
+
+// Modifies a std::vector in-place using a specified bias.
+template<typename T, typename B>
+void ApplyBiasToData(std::vector<T>& v, const std::vector<B>& bias,
+                     float vScale, int32_t vOffset,
+                     float bScale, int32_t bOffset)
+{
+    ARMNN_ASSERT_MSG((armnn::IsQuantizedType<T>() && vScale != 0.0f) || (!armnn::IsQuantizedType<T>()),
+                     "Invalid type and parameter combination.");
+    ARMNN_ASSERT_MSG((armnn::IsQuantizedType<B>() && bScale != 0.0f) || (!armnn::IsQuantizedType<B>()),
+                     "Invalid type and parameter combination.");
+
+    for (uint32_t i = 0; i < bias.size(); ++i)
+    {
+        for (long unsigned int j = i; j < v.size(); j+=bias.size())
+        {
+            // Note we need to dequantize and re-quantize the image value and the bias.
+            float dBias = SelectiveDequantize(bias[i], bScale, bOffset);
+
+            T& outRef = v[j];
+            float dOutput = SelectiveDequantize(outRef, vScale, vOffset);
+            outRef = SelectiveQuantize<T>(dOutput + dBias, vScale, vOffset);
+        }
+    }
+}
+
+// Set the quantization scale and offset values for data types.
+template<armnn::DataType ArmnnType>
+void SetScaleOffset(float& qScale, int32_t& qOffset)
+{
+    switch (ArmnnType)
+    {
+        case armnn::DataType::QAsymmU8:
+        {
+            qScale = 0.1f;
+            qOffset = 128;
+            break;
+        }
+        case armnn::DataType::QAsymmS8:
+        case armnn::DataType::QSymmS16:
+        {
+            qScale = 0.1f;
+            qOffset = 0;
+            break;
+        }
+        case armnn::DataType::BFloat16:
+        case armnn::DataType::Float16:
+        case armnn::DataType::Float32:
+        default:
+        {
+            qScale = 0.f;
+            qOffset = 0;
+            break;
+        }
+    }
+}
+
+// Create a vector from 0 to size and quantize (if required).
+template <typename T>
+std::vector<T> CreateQuantizedData(int32_t size, float qScale, int32_t qOffset)
+{
+    std::vector<float> data;
+    for (int32_t i = 0; i < size; ++i)
+    {
+        data.push_back(static_cast<float>(i));
+    }
+
+    return QuantizedVector<T>(data, qScale, qOffset);
+}
+
+// Create a vector from 0 to size divided and then quantized (if required) to create smaller floating point values.
+template <typename T>
+std::vector<T> CreateSmallQuantizedData(int32_t size, float divisor, float qScale, int32_t qOffset)
+{
+    std::vector<float> data;
+    for (int32_t i = 0; i < size; ++i)
+    {
+        float value = static_cast<float>(i);
+        data.push_back(value/divisor);
+    }
+
+    return QuantizedVector<T>(data, qScale, qOffset);;
+}
+
+//
+// Convolution3d implementations
+//
+
+template<armnn::DataType ArmnnType,
+        armnn::DataType ArmnnBType,
+        typename T = armnn::ResolveType<ArmnnType>,
+        typename B = armnn::ResolveType<ArmnnBType>>
+LayerTestResult<T, 5> SimpleConvolution3dTestImpl(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        const std::vector<T>& input,
+        const std::vector<T>& kernel,
+        const std::vector<B>& bias,
+        const std::vector<T>& outputExpected,
+        const armnn::TensorShape& inputShape,
+        const armnn::TensorShape& kernelShape,
+        const armnn::TensorShape& outputExpectedShape,
+        const armnn::DataLayout dataLayout,
+        float qScale,
+        int32_t qOffset,
+        uint32_t strideX   = 1,
+        uint32_t strideY   = 1,
+        uint32_t strideZ   = 1,
+        uint32_t dilationX = 1,
+        uint32_t dilationY = 1,
+        uint32_t dilationZ = 1,
+        uint32_t padLeft   = 0,
+        uint32_t padTop    = 0,
+        uint32_t padRight  = 0,
+        uint32_t padBottom = 0,
+        uint32_t padFront  = 0,
+        uint32_t padBack   = 0)
+{
+    unsigned int inputNum       = armnn::numeric_cast<unsigned int>(inputShape[0]);
+    unsigned int inputDepth     = armnn::numeric_cast<unsigned int>(inputShape[1]);
+    unsigned int inputHeight    = armnn::numeric_cast<unsigned int>(inputShape[2]);
+    unsigned int inputWidth     = armnn::numeric_cast<unsigned int>(inputShape[3]);
+    unsigned int inputChannels  = armnn::numeric_cast<unsigned int>(inputShape[4]);
+
+    // Conv3d weights/kernel layout: [D,H,W,I,O]
+    unsigned int kernelDepth        = armnn::numeric_cast<unsigned int>(kernelShape[0]);
+    unsigned int kernelHeight       = armnn::numeric_cast<unsigned int>(kernelShape[1]);
+    unsigned int kernelWidth        = armnn::numeric_cast<unsigned int>(kernelShape[2]);
+    unsigned int kernelInChannels   = armnn::numeric_cast<unsigned int>(kernelShape[3]);
+    unsigned int kernelOutChannels  = armnn::numeric_cast<unsigned int>(kernelShape[4]);
+
+    unsigned int outputNum      = armnn::numeric_cast<unsigned int>(outputExpectedShape[0]);
+    unsigned int outputDepth    = armnn::numeric_cast<unsigned int>(outputExpectedShape[1]);
+    unsigned int outputHeight   = armnn::numeric_cast<unsigned int>(outputExpectedShape[2]);
+    unsigned int outputWidth    = armnn::numeric_cast<unsigned int>(outputExpectedShape[3]);
+    unsigned int outputChannels = armnn::numeric_cast<unsigned int>(outputExpectedShape[4]);
+
+    bool biasEnabled = bias.size() > 0;
+
+    // If a bias is used, its size must equal the number of output channels.
+    ARMNN_ASSERT(!biasEnabled || bias.size() == outputChannels);
+
+    // Creates the tensors.
+    armnn::TensorInfo inputTensorInfo({inputNum, inputDepth, inputHeight, inputWidth, inputChannels}, ArmnnType);
+    armnn::TensorInfo outputTensorInfo({outputNum, outputDepth, outputHeight, outputWidth, outputChannels}, ArmnnType);
+    armnn::TensorInfo kernelDesc({kernelDepth, kernelHeight, kernelWidth, kernelInChannels, kernelOutChannels},
+                                 ArmnnType);
+    armnn::TensorInfo biasDesc({static_cast<unsigned int>(bias.size())}, ArmnnBType);
+
+    // Set quantization parameters if the requested type is a quantized type.
+    if(armnn::IsQuantizedType<T>())
+    {
+        inputTensorInfo.SetQuantizationScale(qScale);
+        inputTensorInfo.SetQuantizationOffset(qOffset);
+        outputTensorInfo.SetQuantizationScale(qScale);
+        outputTensorInfo.SetQuantizationOffset(qOffset);
+        kernelDesc.SetQuantizationScale(qScale);
+        kernelDesc.SetQuantizationOffset(qOffset);
+        biasDesc.SetQuantizationScale(qScale*qScale);
+        biasDesc.SetQuantizationOffset(0);
+    }
+
+    // Construct the input data.
+    std::vector<T> inputData;
+    inputData.assign(input.data(), input.data() + inputNum*inputDepth*inputHeight*inputWidth*inputChannels);
+
+    // Construct the output data and apply bias if needed.
+    std::vector<T> outputData;
+    outputData.assign(outputExpected.data(), outputExpected.data() +
+        outputNum*outputDepth*outputHeight*outputWidth*outputChannels);
+
+    if (biasEnabled)
+    {
+        ApplyBiasToData(outputData, bias,
+                        outputTensorInfo.GetQuantizationScale(), outputTensorInfo.GetQuantizationOffset(),
+                        biasDesc.GetQuantizationScale(), biasDesc.GetQuantizationOffset());
+    }
+
+    std::vector<T> actualOutput(outputTensorInfo.GetNumElements());
+
+    std::unique_ptr<armnn::ITensorHandle> inputHandle = tensorHandleFactory.CreateTensorHandle(inputTensorInfo);
+    std::unique_ptr<armnn::ITensorHandle> outputHandle = tensorHandleFactory.CreateTensorHandle(outputTensorInfo);
+
+    armnn::ScopedTensorHandle weightsTensor(kernelDesc);
+    AllocateAndCopyDataToITensorHandle(&weightsTensor, kernel.data());
+
+    armnn::ScopedTensorHandle biasTensor(biasDesc);
+    if (biasEnabled)
+    {
+        AllocateAndCopyDataToITensorHandle(&biasTensor, bias.data());
+    }
+
+    armnn::Convolution3dQueueDescriptor data;
+    data.m_Weight = &weightsTensor;
+    data.m_Bias = &biasTensor; // Still set this whether or not bias is enabled - it can be a source of bugs.
+    data.m_Parameters.m_StrideX = strideX;
+    data.m_Parameters.m_StrideY = strideY;
+    data.m_Parameters.m_StrideZ = strideZ;
+    data.m_Parameters.m_PadLeft = padLeft;
+    data.m_Parameters.m_PadRight = padRight;
+    data.m_Parameters.m_PadTop = padTop;
+    data.m_Parameters.m_PadBottom = padBottom;
+    data.m_Parameters.m_PadFront = padFront;
+    data.m_Parameters.m_PadBack = padBack;
+    data.m_Parameters.m_DilationX = dilationX;
+    data.m_Parameters.m_DilationY = dilationY;
+    data.m_Parameters.m_DilationZ = dilationZ;
+    data.m_Parameters.m_DataLayout = dataLayout;
+    data.m_Parameters.m_BiasEnabled = biasEnabled;
+
+    armnn::WorkloadInfo info;
+    AddInputToWorkload(data, info, inputTensorInfo, inputHandle.get());
+    AddOutputToWorkload(data, info, outputTensorInfo, outputHandle.get());
+
+    std::unique_ptr<armnn::IWorkload> workload = workloadFactory.CreateConvolution3d(data, info);
+    inputHandle->Allocate();
+    outputHandle->Allocate();
+
+    CopyDataToITensorHandle(inputHandle.get(), inputData.data());
+
+    ExecuteWorkload(*workload, memoryManager);
+
+    CopyDataFromITensorHandle(actualOutput.data(), outputHandle.get());
+
+    return LayerTestResult<T, 5>(actualOutput,
+                                 outputData,
+                                 outputHandle->GetShape(),
+                                 outputTensorInfo.GetShape());
+}
+
+template<armnn::DataType ArmnnType,
+        armnn::DataType ArmnnBType,
+        typename T = armnn::ResolveType<ArmnnType>>
+LayerTestResult<T, 5> SimpleConvolution3d3x3x3TestCommon(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled,
+        armnn::DataLayout dataLayout)
+{
+    float qScale;
+    int32_t qOffset;
+    SetScaleOffset<ArmnnType>(qScale, qOffset);
+
+    armnn::TensorInfo inputDesc({ 1, 5, 5, 5, 1 }, ArmnnType);
+    std::vector<T> input = CreateQuantizedData<T>(125, qScale, qOffset);
+
+    armnn::TensorInfo kernelDesc({ 3, 3, 3, 1, 1 }, ArmnnType);
+    std::vector<T> kernel = QuantizedVector<T>(
+    {
+        1, 1, 1,
+        1, 1, 1,
+        1, 1, 1,
+
+        0, 0, 0,
+        0, 1, 0,
+        0, 0, 0,
+
+        1, 1, 1,
+        1, 1, 1,
+        1, 1, 1,
+    },
+    qScale, qOffset);
+
+    armnn::TensorInfo outputDesc({ 1, 3, 3, 3, 1 }, ArmnnType);
+    std::vector<T> outputData = QuantizedVector<T>(
+    {
+        589, 608, 627,
+        684, 703, 722,
+        779, 798, 817,
+
+        1064, 1083, 1102,
+        1159, 1178, 1197,
+        1254, 1273, 1292,
+
+        1539, 1558, 1577,
+        1634, 1653, 1672,
+        1729, 1748, 1767
+    },
+    qScale, qOffset);
+
+    return SimpleConvolution3dTestImpl<ArmnnType, ArmnnBType>(
+            workloadFactory,
+            memoryManager,
+            tensorHandleFactory,
+            input,
+            kernel,
+            GetBiasData<ArmnnBType>(biasEnabled, qScale * qScale, outputDesc, dataLayout),
+            outputData,
+            inputDesc.GetShape(),
+            kernelDesc.GetShape(),
+            outputDesc.GetShape(),
+            dataLayout,
+            qScale,
+            qOffset
+    );
+}
+
+template<armnn::DataType ArmnnType,
+        armnn::DataType ArmnnBType,
+        typename T = armnn::ResolveType<ArmnnType>>
+LayerTestResult<T, 5> Convolution3d2x2x2Strides3x5x5TestCommon(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled,
+        armnn::DataLayout dataLayout)
+{
+    float qScale;
+    int32_t qOffset;
+    SetScaleOffset<ArmnnType>(qScale, qOffset);
+
+    armnn::TensorInfo inputDesc({ 1, 3, 10, 10, 1 }, ArmnnType);
+    std::vector<T> input = CreateQuantizedData<T>(300, qScale, qOffset);
+
+    armnn::TensorInfo kernelDesc({ 3, 5, 5, 1, 1 }, ArmnnType);
+    std::vector<T> kernel = QuantizedVector<T>(
+    {
+        1, 1, 1, 1, 1,
+        1, 1, 1, 1, 1,
+        1, 1, 1, 1, 1,
+        1, 1, 1, 1, 1,
+        1, 1, 1, 1, 1,
+
+        0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0,
+
+        2, 2, 2, 2, 2,
+        2, 2, 2, 2, 2,
+        2, 2, 2, 2, 2,
+        2, 2, 2, 2, 2,
+        2, 2, 2, 2, 2,
+    },
+    qScale, qOffset);
+
+    armnn::TensorInfo outputDesc({ 1, 1, 3, 3, 1 }, ArmnnType);
+    std::vector<T> outputData = QuantizedVector<T>(
+    {
+        11650, 11800, 11950,
+
+        13150, 13300, 13450,
+
+        14650, 14800, 14950
+    },
+    qScale, qOffset);
+
+    return SimpleConvolution3dTestImpl<ArmnnType, ArmnnBType>(
+            workloadFactory,
+            memoryManager,
+            tensorHandleFactory,
+            input,
+            kernel,
+            GetBiasData<ArmnnBType>(biasEnabled, qScale * qScale, outputDesc, dataLayout),
+            outputData,
+            inputDesc.GetShape(),
+            kernelDesc.GetShape(),
+            outputDesc.GetShape(),
+            dataLayout,
+            qScale,
+            qOffset,
+            2, // strideX
+            2, // strideY
+            2  // strideZ
+    );
+}
+
+template<armnn::DataType ArmnnType,
+        armnn::DataType ArmnnBType,
+        typename T = armnn::ResolveType<ArmnnType>>
+LayerTestResult<T, 5> Convolution3d2x2x2Dilation2x2x2TestCommon(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled,
+        armnn::DataLayout dataLayout)
+{
+    float qScale;
+    int32_t qOffset;
+    SetScaleOffset<ArmnnType>(qScale, qOffset);
+
+    armnn::TensorInfo inputDesc({ 1, 5, 5, 5, 2 }, ArmnnType);
+    std::vector<T> input = CreateQuantizedData<T>(250, qScale, qOffset);
+
+    armnn::TensorInfo kernelDesc({ 2, 2, 2, 2, 2 }, ArmnnType);
+    std::vector<T> kernel = QuantizedVector<T>(
+    {
+        -1, -1,  -1, -1,  -1, -1,  -1, -1,  -1, -1,  -1,  1,   1,  1,  -1, -1,
+         1,  1,  -1,  1,  -1,  1,  -1,  1,  -1, -1,  -1,  1,  -1,  1,  -1,  1,
+    },
+    qScale, qOffset);
+
+    // Since the dilation rate is 3 this will dilate the kernel to be 4x4,
+    // therefore the output will be 2x2
+    armnn::TensorInfo outputDesc({ 1, 2, 2, 2, 2 }, ArmnnType);
+    std::vector<T> outputData = QuantizedVector<T>(
+    {
+        -1124, 974,
+        -1148, 978,
+
+        -1244, 994,
+        -1268, 998,
+
+        -1724, 1074,
+        -1748, 1078,
+
+        -1844, 1094,
+        -1868, 1098
+    },
+    qScale, qOffset);
+
+    return SimpleConvolution3dTestImpl<ArmnnType, ArmnnBType>(
+            workloadFactory,
+            memoryManager,
+            tensorHandleFactory,
+            input,
+            kernel,
+            GetBiasData<ArmnnBType>(biasEnabled, qScale * qScale, outputDesc, dataLayout),
+            outputData,
+            inputDesc.GetShape(),
+            kernelDesc.GetShape(),
+            outputDesc.GetShape(),
+            dataLayout,
+            qScale,
+            qOffset,
+            1, // strideX
+            1, // strideY
+            1, // strideZ
+            3, // dilationX
+            3, // dilationY
+            3 // dilationZ
+    );
+}
+
+template<armnn::DataType ArmnnType,
+        armnn::DataType ArmnnBType,
+        typename T = armnn::ResolveType<ArmnnType>>
+LayerTestResult<T, 5> Convolution3dPaddingSame3x3x3TestCommon(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled,
+        armnn::DataLayout dataLayout)
+{
+    float qScale;
+    int32_t qOffset;
+    SetScaleOffset<ArmnnType>(qScale, qOffset);
+
+    armnn::TensorInfo inputDesc({ 1, 5, 5, 5, 1 }, ArmnnType);
+    std::vector<T> input = CreateQuantizedData<T>(125, qScale, qOffset);
+
+    armnn::TensorInfo kernelDesc({ 3, 3, 3, 1, 1 }, ArmnnType);
+    std::vector<T> kernel = QuantizedVector<T>(
+    {
+        1, 1, 1,
+        1, 1, 1,
+        1, 1, 1,
+
+        0, 0, 0,
+        0, 0, 0,
+        0, 0, 0,
+
+        1, 1, 1,
+        1, 1, 1,
+        1, 1, 1,
+    },
+    qScale, qOffset);
+
+    armnn::TensorInfo outputDesc({ 1, 5, 5, 5, 1 }, ArmnnType);
+    std::vector<T> outputData = QuantizedVector<T>(
+    {
+        112, 171, 177, 183, 124,
+        183, 279, 288, 297, 201,
+        213, 324, 333, 342, 231,
+        243, 369, 378, 387, 261,
+        172, 261, 267, 273, 184,
+
+        224, 342, 354, 366, 248,
+        366, 558, 576, 594, 402,
+        426, 648, 666, 684, 462,
+        486, 738, 756, 774, 522,
+        344, 522, 534, 546, 368,
+
+        424, 642,  654,  666,  448,
+        666, 1008, 1026, 1044, 702,
+        726, 1098, 1116, 1134, 762,
+        786, 1188, 1206, 1224, 822,
+        544, 822,  834,  846,  568,
+        624, 942,  954,  966,  648,
+
+        966,  1458, 1476, 1494, 1002,
+        1026, 1548, 1566, 1584, 1062,
+        1086, 1638, 1656, 1674, 1122,
+        744,  1122, 1134, 1146, 768,
+        312,  471,  477,  483,  324,
+        483,  729,  738,  747,  501,
+        513,  774,  783,  792,  531,
+        543,  819,  828,  837,  561,
+        372,  561,  567,  573,  384
+    },
+    qScale, qOffset);
+
+    return SimpleConvolution3dTestImpl<ArmnnType, ArmnnBType>(
+            workloadFactory,
+            memoryManager,
+            tensorHandleFactory,
+            input,
+            kernel,
+            GetBiasData<ArmnnBType>(biasEnabled, qScale * qScale, outputDesc, dataLayout),
+            outputData,
+            inputDesc.GetShape(),
+            kernelDesc.GetShape(),
+            outputDesc.GetShape(),
+            dataLayout,
+            qScale,
+            qOffset,
+            1, // strideX
+            1, // strideY
+            1, // strideZ
+            1, // dilationX
+            1, // dilationY
+            1, // dilationZ
+            1, // padLeft
+            1, // padTop
+            1, // padRight
+            1, // padBottom
+            1, // padFront
+            1 // padBack
+    );
+}
+
+LayerTestResult<float, 5> Convolution3dStrideDilationPadding3x3x3TestCommonFloat32(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled,
+        armnn::DataLayout dataLayout)
+{
+    float qScale = 0.f;
+    int32_t qOffset = 0;
+
+    armnn::TensorInfo inputDesc({ 1, 3, 10, 10, 2 }, armnn::DataType::Float32);
+    std::vector<float> input = CreateSmallQuantizedData<float>(600, 100.0f, qScale, qOffset);
+
+    armnn::TensorInfo kernelDesc({ 3, 3, 3, 2, 2 }, armnn::DataType::Float32);
+    std::vector<float> kernel = CreateSmallQuantizedData<float>(108, 100.0f, qScale, qOffset);
+
+    // Since the dilation rate is 2 this will dilate the kernel to be 5x5: d(K-1)+1 --> 2 x (3-1) + 1 = 5,
+    // therefore the output will be 1x4x4: (I − K + 2P)/S +1 => trunc((10 - 3 + 2x2 )/3 + 1))
+    // where, dilation size = d = 2; kernel size = K = 3; input size = I = 10; padding size = P = 2; stride = S = 3
+    armnn::TensorInfo outputDesc({ 1, 1, 4, 4, 2 }, armnn::DataType::Float32);
+    std::vector<float> outputData =
+    {
+        12.0312f, 12.2268f, 17.7512f, 18.0494f,
+        18.176f,  18.4814f, 5.6912f,  5.7938f,
+        19.1664f, 19.5078f, 28.119f,  28.6383f,
+        28.6914f, 29.2215f, 8.9094f,  9.0873f,
+
+        23.1264f, 23.5398f, 33.843f,  34.4703f,
+        34.4154f, 35.0535f, 10.6734f, 10.8873f,
+        6.2712f,  6.417f,   9.0718f,  9.2929f,
+        9.2194f,  9.4441f,  2.7862f,  2.8615f
+    };
+
+    return SimpleConvolution3dTestImpl<armnn::DataType::Float32, armnn::DataType::Float32>(
+            workloadFactory,
+            memoryManager,
+            tensorHandleFactory,
+            input,
+            kernel,
+            GetBiasData<armnn::DataType::Float32>(biasEnabled, qScale * qScale, outputDesc, dataLayout),
+            outputData,
+            inputDesc.GetShape(),
+            kernelDesc.GetShape(),
+            outputDesc.GetShape(),
+            dataLayout,
+            qScale,
+            qOffset,
+            3, // strideX
+            3, // strideY
+            3, // strideZ
+            2, // dilationX
+            2, // dilationY
+            2, // dilationZ
+            1, // padLeft
+            1, // padTop
+            1, // padRight
+            1, // padBottom
+            1, // padFront
+            1 // padBack
+    );
+}
+
+LayerTestResult<float, 5> Convolution3d2x2x2Stride3x3x3SmallTestCommonFloat32(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled,
+        armnn::DataLayout dataLayout)
+{
+    float qScale = 0.f;
+    int32_t qOffset = 0;
+
+    armnn::TensorInfo inputDesc({ 1, 3, 10, 10, 1 }, armnn::DataType::Float32);
+    std::vector<float> input = CreateSmallQuantizedData<float>(300, 100.0f, qScale, qOffset);
+
+    armnn::TensorInfo kernelDesc({ 3, 3, 3, 1, 1 }, armnn::DataType::Float32);
+    std::vector<float> kernel =
+    {
+         0.125977f,  0.150391f,  0.101562f,
+         0.0585938f, 0.0864258f, 0.043457f,
+         0.034668f,  0.0322266f, 0.0385742f,
+
+         0.125977f,  0.150391f, -0.101562f,
+        -0.0585938f,-0.0864258f,-0.043457f,
+        -0.0104630f, 0.0154114f, 0.0013768f,
+
+         0.0344238f, 0.035644f,  0.0495605f,
+         0.0683594f, 0.099121f, -0.0461426f,
+        -0.0996094f,-0.126953f, -0.043457f,
+    };
+
+    armnn::TensorInfo outputDesc({ 1, 1, 4, 4, 1 }, armnn::DataType::Float32);
+    std::vector<float> outputData =
+    {
+        -0.08156067f, -0.06891209f, -0.05589598f, -0.04310101f,
+        0.04584253f,   0.05855697f,  0.07129729f,  0.08325434f,
+        0.17304349f,   0.18521416f,  0.19818866f,  0.21096253f,
+        0.29965734f,   0.312698f,    0.32547557f,  0.33818722f
+    };
+
+    return SimpleConvolution3dTestImpl<armnn::DataType::Float32, armnn::DataType::Float32>(
+            workloadFactory,
+            memoryManager,
+            tensorHandleFactory,
+            input,
+            kernel,
+            GetBiasData<armnn::DataType::Float32>(biasEnabled, qScale * qScale, outputDesc, dataLayout),
+            outputData,
+            inputDesc.GetShape(),
+            kernelDesc.GetShape(),
+            outputDesc.GetShape(),
+            dataLayout,
+            qScale,
+            qOffset,
+            2, // strideX
+            2, // strideY
+            2  // strideZ
+    );
+}
+
+LayerTestResult<armnn::Half, 5> Convolution3d2x3x3TestCommonFloat16(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled,
+        armnn::DataLayout dataLayout)
+{
+    using namespace half_float::literal;
+
+    float qScale = 0.f;
+    int32_t qOffset = 0;
+
+    armnn::TensorInfo inputDesc({ 1, 2, 3, 3, 2 }, armnn::DataType::Float16);
+    const std::vector<armnn::Half> input =
+    {
+        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
+    };
+
+    armnn::TensorInfo kernelDesc({ 2, 2, 2, 2, 2 }, armnn::DataType::Float16);
+    std::vector<armnn::Half> kernel =
+    {
+        -1._h, -1._h,  -1._h, -1._h,  -1._h, -1._h,  -1._h, -1._h,
+        -1._h, -1._h,  -1._h,  1._h,   1._h,  1._h,  -1._h, -1._h,
+         1._h,  1._h,  -1._h,  1._h,  -1._h,  1._h,  -1._h,  1._h,
+        -1._h, -1._h,  -1._h,  1._h,  -1._h,  1._h,  -1._h,  1._h,
+    };
+
+    armnn::TensorInfo outputDesc({ 1, 1, 2, 2, 2 }, armnn::DataType::Float16);
+    std::vector<armnn::Half> outputData =
+    {
+        -176._h,  128._h,
+        -200._h,  132._h,
+
+        -248._h,  140._h,
+        -272._h,  144._h
+    };
+
+    return SimpleConvolution3dTestImpl<armnn::DataType::Float16, armnn::DataType::Float16>(
+            workloadFactory,
+            memoryManager,
+            tensorHandleFactory,
+            input,
+            kernel,
+            GetBiasData<armnn::DataType::Float16>(biasEnabled, qScale * qScale, outputDesc, dataLayout),
+            outputData,
+            inputDesc.GetShape(),
+            kernelDesc.GetShape(),
+            outputDesc.GetShape(),
+            dataLayout,
+            qScale,
+            qOffset
+    );
+}
+
+LayerTestResult<armnn::Half, 5> Convolution3d2x2x2SmallTestCommonFloat16(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled,
+        armnn::DataLayout dataLayout)
+{
+    using namespace half_float::literal;
+
+    float qScale = 0.f;
+    int32_t qOffset = 0;
+
+    armnn::TensorInfo inputDesc({ 1, 2, 4, 4, 1 }, armnn::DataType::Float16);
+    const std::vector<armnn::Half> input =
+    {
+        0.0367984_h, 0.0380895_h, 0.0420157_h,  0.0675631_h,
+        0.0938920_h, 0.0476106_h, 0.1035490_h,  0.1260370_h,
+        0.0461647_h, 0.0883828_h, 0.1159540_h,  0.0498519_h,
+        0.0104630_h, 0.0154114_h, 0.00137681_h, 0.0344238_h,
+
+        0.0356445_h, 0.0495605_h, 0.0683594_h,  0.0991211_h,
+        0.0461426_h, 0.0996094_h, 0.1269530_h,  0.0393066_h,
+        0.103516_h,  0.032544_h,  0.124334_h,   0.0564566_h,
+        0.0123544_h, 0.0461647_h, 0.0883828_h,  0.1159540_h,
+    };
+
+    armnn::TensorInfo kernelDesc({ 2, 2, 2, 1, 1 }, armnn::DataType::Float16);
+    std::vector<armnn::Half> kernel =
+    {
+        -0.126184_h, -0.150468_h,
+        -0.101412_h, -0.0586369_h,
+
+        -0.0435089_h, 0.0347555_h,
+         0.0323111_h, 0.0385381_h
+    };
+
+    armnn::TensorInfo outputDesc({ 1, 1, 3, 3, 1 }, armnn::DataType::Float16);
+    std::vector<armnn::Half> outputData =
+    {
+        -0.01718917_h, -0.01370182_h, -0.02727737_h,
+
+        -0.02282543_h, -0.03144084_h, -0.04468598_h,
+
+        -0.02228982_h, -0.02244923_h, -0.02042268_h
+    };
+
+    return SimpleConvolution3dTestImpl<armnn::DataType::Float16, armnn::DataType::Float16>(
+            workloadFactory,
+            memoryManager,
+            tensorHandleFactory,
+            input,
+            kernel,
+            GetBiasData<armnn::DataType::Float16>(biasEnabled, qScale * qScale, outputDesc, dataLayout),
+            outputData,
+            inputDesc.GetShape(),
+            kernelDesc.GetShape(),
+            outputDesc.GetShape(),
+            dataLayout,
+            qScale,
+            qOffset
+    );
+}
+
+LayerTestResult<float, 5> SimpleConvolution3d3x3x3Float32Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return SimpleConvolution3d3x3x3TestCommon<armnn::DataType::Float32, armnn::DataType::Float32>(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<int8_t, 5> SimpleConvolution3d3x3x3Int8Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return SimpleConvolution3d3x3x3TestCommon<armnn::DataType::QAsymmS8, armnn::DataType::Signed32>(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<uint8_t, 5> SimpleConvolution3d3x3x3Uint8Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return SimpleConvolution3d3x3x3TestCommon<armnn::DataType::QAsymmU8, armnn::DataType::Signed32>(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<int16_t, 5> SimpleConvolution3d3x3x3Int16Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return SimpleConvolution3d3x3x3TestCommon<armnn::DataType::QSymmS16, armnn::DataType::Signed32>(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+
+LayerTestResult<float, 5> Convolution3d2x2x2Strides3x5x5Float32Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return Convolution3d2x2x2Strides3x5x5TestCommon<armnn::DataType::Float32, armnn::DataType::Float32>(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<int8_t, 5> Convolution3d2x2x2Strides3x5x5Int8Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return Convolution3d2x2x2Strides3x5x5TestCommon<armnn::DataType::QAsymmS8, armnn::DataType::Signed32>(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<uint8_t, 5> Convolution3d2x2x2Strides3x5x5Uint8Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return Convolution3d2x2x2Strides3x5x5TestCommon<armnn::DataType::QAsymmU8, armnn::DataType::Signed32>(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<int16_t, 5> Convolution3d2x2x2Strides3x5x5Int16Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return Convolution3d2x2x2Strides3x5x5TestCommon<armnn::DataType::QSymmS16, armnn::DataType::Signed32>(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<float, 5> Convolution3d2x2x2Dilation2x2x2Float32Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return Convolution3d2x2x2Dilation2x2x2TestCommon<armnn::DataType::Float32, armnn::DataType::Float32>(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<int8_t, 5> Convolution3d2x2x2Dilation2x2x2Int8Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return Convolution3d2x2x2Dilation2x2x2TestCommon<armnn::DataType::QAsymmS8, armnn::DataType::Signed32>(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<uint8_t, 5> Convolution3d2x2x2Dilation2x2x2Uint8Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return Convolution3d2x2x2Dilation2x2x2TestCommon<armnn::DataType::QAsymmU8, armnn::DataType::Signed32>(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<int16_t, 5> Convolution3d2x2x2Dilation2x2x2Int16Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return Convolution3d2x2x2Dilation2x2x2TestCommon<armnn::DataType::QSymmS16, armnn::DataType::Signed32>(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<float, 5> Convolution3dPaddingSame3x3x3Float32Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return Convolution3dPaddingSame3x3x3TestCommon<armnn::DataType::Float32, armnn::DataType::Float32>(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<int8_t, 5> Convolution3dPaddingSame3x3x3Int8Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return Convolution3dPaddingSame3x3x3TestCommon<armnn::DataType::QAsymmS8, armnn::DataType::Signed32>(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<uint8_t, 5> Convolution3dPaddingSame3x3x3Uint8Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return Convolution3dPaddingSame3x3x3TestCommon<armnn::DataType::QAsymmU8, armnn::DataType::Signed32>(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<int16_t, 5> Convolution3dPaddingSame3x3x3Int16Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return Convolution3dPaddingSame3x3x3TestCommon<armnn::DataType::QSymmS16, armnn::DataType::Signed32>(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<float, 5> Convolution3dStrideDilationPadding3x3x3Float32Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return Convolution3dStrideDilationPadding3x3x3TestCommonFloat32(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<float, 5> Convolution3d2x2x2Stride3x3x3SmallFloat32Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return Convolution3d2x2x2Stride3x3x3SmallTestCommonFloat32(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<armnn::Half, 5> Convolution3d2x3x3Float16Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return Convolution3d2x3x3TestCommonFloat16(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
+
+LayerTestResult<armnn::Half, 5> Convolution3d2x2x2SmallFloat16Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled)
+{
+    return Convolution3d2x2x2SmallTestCommonFloat16(
+            workloadFactory, memoryManager, tensorHandleFactory, biasEnabled, armnn::DataLayout::NDHWC);
+}
diff --git a/src/backends/backendsCommon/test/layerTests/Conv3dTestImpl.hpp b/src/backends/backendsCommon/test/layerTests/Conv3dTestImpl.hpp
new file mode 100644
index 0000000..45a6786
--- /dev/null
+++ b/src/backends/backendsCommon/test/layerTests/Conv3dTestImpl.hpp
@@ -0,0 +1,141 @@
+//
+// Copyright © 2021 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include "LayerTestResult.hpp"
+
+#include <Half.hpp>
+
+#include <ResolveType.hpp>
+
+#include <armnn/Types.hpp>
+
+#include <armnn/backends/IBackendInternal.hpp>
+#include <backendsCommon/WorkloadFactory.hpp>
+
+//
+// Convolution3d
+//
+
+LayerTestResult<float, 5> SimpleConvolution3d3x3x3Float32Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<int8_t , 5> SimpleConvolution3d3x3x3Int8Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<u_int8_t, 5> SimpleConvolution3d3x3x3Uint8Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<int16_t, 5> SimpleConvolution3d3x3x3Int16Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<float, 5> Convolution3d2x2x2Strides3x5x5Float32Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<int8_t , 5> Convolution3d2x2x2Strides3x5x5Int8Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<u_int8_t, 5> Convolution3d2x2x2Strides3x5x5Uint8Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<int16_t, 5> Convolution3d2x2x2Strides3x5x5Int16Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<float, 5> Convolution3d2x2x2Dilation2x2x2Float32Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<int8_t , 5> Convolution3d2x2x2Dilation2x2x2Int8Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<u_int8_t, 5> Convolution3d2x2x2Dilation2x2x2Uint8Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<int16_t, 5> Convolution3d2x2x2Dilation2x2x2Int16Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<float, 5> Convolution3dPaddingSame3x3x3Float32Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<int8_t , 5> Convolution3dPaddingSame3x3x3Int8Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<u_int8_t, 5> Convolution3dPaddingSame3x3x3Uint8Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<int16_t, 5> Convolution3dPaddingSame3x3x3Int16Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<float, 5> Convolution3dStrideDilationPadding3x3x3Float32Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<float, 5> Convolution3d2x2x2Stride3x3x3SmallFloat32Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<armnn::Half, 5> Convolution3d2x3x3Float16Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
+
+LayerTestResult<armnn::Half, 5> Convolution3d2x2x2SmallFloat16Test(
+        armnn::IWorkloadFactory& workloadFactory,
+        const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+        const armnn::ITensorHandleFactory& tensorHandleFactory,
+        bool biasEnabled);
diff --git a/src/backends/reference/RefLayerSupport.cpp b/src/backends/reference/RefLayerSupport.cpp
index aaf9aa0..c0ede67 100644
--- a/src/backends/reference/RefLayerSupport.cpp
+++ b/src/backends/reference/RefLayerSupport.cpp
@@ -605,6 +605,76 @@
     return supported;
 }
 
+bool RefLayerSupport::IsConvolution3dSupported(const TensorInfo& input,
+                                               const TensorInfo& output,
+                                               const Convolution3dDescriptor& descriptor,
+                                               const TensorInfo& weights,
+                                               const Optional<TensorInfo>& biases,
+                                               Optional<std::string&> reasonIfUnsupported) const
+{
+    bool supported = true;
+
+    // Define supported types.
+    std::array<DataType,7> supportedTypes =
+    {
+        DataType::BFloat16,
+        DataType::Float32,
+        DataType::Float16,
+        DataType::QAsymmS8,
+        DataType::QAsymmU8,
+        DataType::QSymmS8,
+        DataType::QSymmS16
+    };
+
+    supported &= CheckSupportRule(TypeAnyOf(input, supportedTypes), reasonIfUnsupported,
+                                  "Reference Convolution3d: input is not a supported type.");
+
+    supported &= CheckSupportRule(TypeAnyOf(output, supportedTypes), reasonIfUnsupported,
+                                  "Reference Convolution3d: output is not a supported type.");
+
+    supported &= CheckSupportRule(TypesAreEqual(input, output), reasonIfUnsupported,
+                                  "Reference Convolution3d: input and output types mismatched.");
+
+    const DataType inputType = input.GetDataType();
+    if (IsQuantized8BitType(inputType))
+    {
+        std::array<DataType, 3> supportedWeightTypes =
+        {
+            DataType::QAsymmS8,
+            DataType::QAsymmU8,
+            DataType::QSymmS8
+        };
+
+        supported &= CheckSupportRule(TypeAnyOf(weights, supportedWeightTypes), reasonIfUnsupported,
+                                      "Reference Convolution3d: weights type not supported for quantized input.");
+    }
+    else
+    {
+        supported &= CheckSupportRule(TypeAnyOf(weights, supportedTypes), reasonIfUnsupported,
+                                      "Reference Convolution3d: weights is not a supported type.");
+
+        supported &= CheckSupportRule(TypesAreEqual(input, weights), reasonIfUnsupported,
+                                      "Reference Convolution3d: input and weights types mismatched.");
+    }
+
+    if (biases.has_value())
+    {
+        std::array<DataType,4> biasesSupportedTypes =
+        {
+            DataType::BFloat16,
+            DataType::Float32,
+            DataType::Float16,
+            DataType::Signed32
+        };
+
+        supported &= CheckSupportRule(TypeAnyOf(biases.value(), biasesSupportedTypes), reasonIfUnsupported,
+                                      "Reference Convolution3d: biases is not a supported type.");
+    }
+    IgnoreUnused(descriptor);
+
+    return supported;
+}
+
 bool RefLayerSupport::IsDebugSupported(const TensorInfo& input,
                                        const TensorInfo& output,
                                        Optional<std::string&> reasonIfUnsupported) const
diff --git a/src/backends/reference/RefLayerSupport.hpp b/src/backends/reference/RefLayerSupport.hpp
index 2693dc1..627418e 100644
--- a/src/backends/reference/RefLayerSupport.hpp
+++ b/src/backends/reference/RefLayerSupport.hpp
@@ -92,6 +92,13 @@
                                   const Optional<TensorInfo>& biases,
                                   Optional<std::string&> reasonIfUnsupported = EmptyOptional()) const override;
 
+    bool IsConvolution3dSupported(const TensorInfo& input,
+                                  const TensorInfo& output,
+                                  const Convolution3dDescriptor& descriptor,
+                                  const TensorInfo& weights,
+                                  const Optional<TensorInfo>& biases,
+                                  Optional<std::string&> reasonIfUnsupported = EmptyOptional()) const override;
+
     bool IsDebugSupported(const TensorInfo& input,
                           const TensorInfo& output,
                           Optional<std::string&> reasonIfUnsupported = EmptyOptional()) const override;
diff --git a/src/backends/reference/RefWorkloadFactory.cpp b/src/backends/reference/RefWorkloadFactory.cpp
index 681b73a..18a5af2 100644
--- a/src/backends/reference/RefWorkloadFactory.cpp
+++ b/src/backends/reference/RefWorkloadFactory.cpp
@@ -241,6 +241,12 @@
     return std::make_unique<RefConvolution2dWorkload>(descriptor, info);
 }
 
+std::unique_ptr<IWorkload> RefWorkloadFactory::CreateConvolution3d(const Convolution3dQueueDescriptor& descriptor,
+                                                                   const WorkloadInfo& info) const
+{
+    return std::make_unique<RefConvolution3dWorkload>(descriptor, info);
+}
+
 std::unique_ptr<IWorkload> RefWorkloadFactory::CreateDebug(const DebugQueueDescriptor& descriptor,
                                                            const WorkloadInfo& info) const
 {
diff --git a/src/backends/reference/RefWorkloadFactory.hpp b/src/backends/reference/RefWorkloadFactory.hpp
index fe3eb54..d00d3ca 100644
--- a/src/backends/reference/RefWorkloadFactory.hpp
+++ b/src/backends/reference/RefWorkloadFactory.hpp
@@ -115,6 +115,9 @@
     std::unique_ptr<IWorkload> CreateConvolution2d(const Convolution2dQueueDescriptor& descriptor,
                                                    const WorkloadInfo& info) const override;
 
+    std::unique_ptr<IWorkload> CreateConvolution3d(const Convolution3dQueueDescriptor& descriptor,
+                                                   const WorkloadInfo& info) const override;
+
     std::unique_ptr<IWorkload> CreateDebug(const DebugQueueDescriptor& descriptor,
                                            const WorkloadInfo& info) const override;
 
diff --git a/src/backends/reference/backend.mk b/src/backends/reference/backend.mk
index 2dc2bc4..7d6c59a 100644
--- a/src/backends/reference/backend.mk
+++ b/src/backends/reference/backend.mk
@@ -27,6 +27,7 @@
         workloads/BatchToSpaceNd.cpp \
         workloads/Broadcast.cpp \
         workloads/ConvImpl.cpp \
+        workloads/Conv3dImpl.cpp \
         workloads/Debug.cpp \
         workloads/DepthToSpace.cpp \
         workloads/DetectionPostProcess.cpp \
@@ -58,6 +59,7 @@
         workloads/RefConvertFp32ToBf16Workload.cpp \
         workloads/RefConvertFp32ToFp16Workload.cpp \
         workloads/RefConvolution2dWorkload.cpp \
+        workloads/RefConvolution3dWorkload.cpp \
         workloads/RefDebugWorkload.cpp \
         workloads/RefDepthToSpaceWorkload.cpp \
         workloads/RefDepthwiseConvolution2dWorkload.cpp \
diff --git a/src/backends/reference/test/RefLayerTests.cpp b/src/backends/reference/test/RefLayerTests.cpp
index 4afee79..f5d388d 100644
--- a/src/backends/reference/test/RefLayerTests.cpp
+++ b/src/backends/reference/test/RefLayerTests.cpp
@@ -208,6 +208,39 @@
                      false,
                      DataLayout::NHWC);
 
+// Convolution 3d
+ARMNN_AUTO_TEST_CASE_WITH_THF(SimpleConvolution3d3x3x3Float32, SimpleConvolution3d3x3x3Float32Test, false)
+ARMNN_AUTO_TEST_CASE_WITH_THF(SimpleConvolution3d3x3x3Int8, SimpleConvolution3d3x3x3Int8Test, false)
+ARMNN_AUTO_TEST_CASE_WITH_THF(SimpleConvolution3d3x3x3Uint8, SimpleConvolution3d3x3x3Uint8Test, false)
+ARMNN_AUTO_TEST_CASE_WITH_THF(SimpleConvolution3d3x3x3Int16, SimpleConvolution3d3x3x3Int16Test, false)
+
+ARMNN_AUTO_TEST_CASE_WITH_THF(Convolution3d2x2x2Strides3x5x5Float32, Convolution3d2x2x2Strides3x5x5Float32Test, false)
+ARMNN_AUTO_TEST_CASE_WITH_THF(Convolution3d2x2x2Strides3x5x5TestInt8, Convolution3d2x2x2Strides3x5x5Int8Test, true)
+ARMNN_AUTO_TEST_CASE_WITH_THF(Convolution3d2x2x2Strides3x5x5TestUint8, Convolution3d2x2x2Strides3x5x5Uint8Test, false)
+ARMNN_AUTO_TEST_CASE_WITH_THF(Convolution3d2x2x2Strides3x5x5TestInt16, Convolution3d2x2x2Strides3x5x5Int16Test, true)
+
+ARMNN_AUTO_TEST_CASE_WITH_THF(Convolution3dPaddingSame3x3x3Float32, Convolution3dPaddingSame3x3x3Float32Test, false)
+ARMNN_AUTO_TEST_CASE_WITH_THF(Convolution3dPaddingSame3x3x3TestInt8, Convolution3dPaddingSame3x3x3Int8Test, false)
+ARMNN_AUTO_TEST_CASE_WITH_THF(Convolution3dPaddingSame3x3x3TestUint8, Convolution3dPaddingSame3x3x3Uint8Test, false)
+ARMNN_AUTO_TEST_CASE_WITH_THF(Convolution3dPaddingSame3x3x3TestInt16, Convolution3dPaddingSame3x3x3Int16Test, false)
+
+ARMNN_AUTO_TEST_CASE_WITH_THF(Convolution3d2x2x2Dilation2x2x2Float32, Convolution3d2x2x2Dilation2x2x2Float32Test, true)
+ARMNN_AUTO_TEST_CASE_WITH_THF(Convolution3d2x2x2Dilation2x2x2TestInt8, Convolution3d2x2x2Dilation2x2x2Int8Test, true)
+ARMNN_AUTO_TEST_CASE_WITH_THF(Convolution3d2x2x2Dilation2x2x2TestUint8, Convolution3d2x2x2Dilation2x2x2Uint8Test, true)
+ARMNN_AUTO_TEST_CASE_WITH_THF(Convolution3d2x2x2Dilation2x2x2TestInt16, Convolution3d2x2x2Dilation2x2x2Int16Test, true)
+
+ARMNN_AUTO_TEST_CASE_WITH_THF(Convolution3dStrideDilationPadding3x3x3Float32,
+                              Convolution3dStrideDilationPadding3x3x3Float32Test,
+                              true)
+
+ARMNN_AUTO_TEST_CASE_WITH_THF(Convolution3d2x2x2Stride3x3x3SmallTestFloat32,
+                              Convolution3d2x2x2Stride3x3x3SmallFloat32Test,
+                              false)
+
+ARMNN_AUTO_TEST_CASE_WITH_THF(Convolution3d2x3x3TestFloat16, Convolution3d2x3x3Float16Test, true)
+ARMNN_AUTO_TEST_CASE_WITH_THF(Convolution3d2x2x2SmallTestFloat16, Convolution3d2x2x2SmallFloat16Test, false)
+
+
 // Depthwise Convolution
 ARMNN_AUTO_TEST_CASE_WITH_THF(DepthwiseConvolution2d, DepthwiseConvolution2dTest, true, DataLayout::NCHW)
 ARMNN_AUTO_TEST_CASE_WITH_THF(DepthwiseConvolution2dUint8, DepthwiseConvolution2dUint8Test, true, DataLayout::NCHW)
diff --git a/src/backends/reference/workloads/CMakeLists.txt b/src/backends/reference/workloads/CMakeLists.txt
index 0ab8c6b..e169c03 100644
--- a/src/backends/reference/workloads/CMakeLists.txt
+++ b/src/backends/reference/workloads/CMakeLists.txt
@@ -18,6 +18,8 @@
     Broadcast.hpp
     ConvImpl.cpp
     ConvImpl.hpp
+    Conv3dImpl.cpp
+    Conv3dImpl.hpp
     Debug.cpp
     Debug.hpp
     Decoders.hpp
@@ -87,6 +89,8 @@
     RefConvertFp32ToFp16Workload.hpp
     RefConvolution2dWorkload.cpp
     RefConvolution2dWorkload.hpp
+    RefConvolution3dWorkload.cpp
+    RefConvolution3dWorkload.hpp
     RefElementwiseWorkload.cpp
     RefElementwiseWorkload.hpp
     RefDebugWorkload.cpp
diff --git a/src/backends/reference/workloads/Conv3dImpl.cpp b/src/backends/reference/workloads/Conv3dImpl.cpp
new file mode 100644
index 0000000..484d887
--- /dev/null
+++ b/src/backends/reference/workloads/Conv3dImpl.cpp
@@ -0,0 +1,151 @@
+//
+// Copyright © 2021 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "Conv3dImpl.hpp"
+
+namespace armnn
+{
+
+void Convolve3d(const TensorShape& rInputShape,
+                Decoder<float>& rInputDecoder,
+                const TensorShape& rOutputShape,
+                Encoder<float>& rOutputEncoder,
+                const TensorShape& rFilterShape,
+                Decoder<float>& rFilterDecoder,
+                bool biasEnabled,
+                Decoder<float>* pBiasDecoder,
+                DataLayout dataLayout,
+                unsigned int paddingTop,
+                unsigned int paddingLeft,
+                unsigned int paddingFront,
+                unsigned int xStride,
+                unsigned int yStride,
+                unsigned int zStride,
+                unsigned int xDilation,
+                unsigned int yDilation,
+                unsigned int zDilation)
+{
+    if (biasEnabled && !pBiasDecoder)
+    {
+        throw InvalidArgumentException("Bias is enabled but the bias data is invalid");
+    }
+    const armnnUtils::DataLayoutIndexed dataLayoutIndexed(dataLayout);
+
+    const unsigned int channelsIndex = dataLayoutIndexed.GetChannelsIndex();
+    const unsigned int heightIndex   = dataLayoutIndexed.GetHeightIndex();
+    const unsigned int widthIndex    = dataLayoutIndexed.GetWidthIndex();
+    const unsigned int depthIndex    = dataLayoutIndexed.GetDepthIndex();
+
+    const unsigned int inChannels   = rInputShape[channelsIndex];
+    const unsigned int outChannels  = rOutputShape[channelsIndex];
+
+    const unsigned int batchSize    = rOutputShape[0];
+    const unsigned int outputHeight = rOutputShape[heightIndex];
+    const unsigned int outputWidth  = rOutputShape[widthIndex];
+    const unsigned int outputDepth  = rOutputShape[depthIndex];
+    const unsigned int inputHeight  = rInputShape[heightIndex];
+    const unsigned int inputWidth   = rInputShape[widthIndex];
+    const unsigned int inputDepth   = rInputShape[depthIndex];
+
+    // Conv3d weights layout: [D,H,W,I,O]
+    const unsigned int filterDepth  = rFilterShape[0];
+    const unsigned int filterHeight = rFilterShape[1];
+    const unsigned int filterWidth  = rFilterShape[2];
+
+    const std::vector<float> inputVec = rInputDecoder.DecodeTensor(rInputShape);
+    const std::vector<float> filterVec = rFilterDecoder.DecodeTensor(rFilterShape);
+
+    const TensorShape biasShape{outChannels};
+    const std::vector<float> biasVec = biasEnabled ? pBiasDecoder->DecodeTensor(biasShape) : std::vector<float>();
+
+    for (unsigned int batchIdx = 0; batchIdx < batchSize; batchIdx++)
+    {
+        for (unsigned int zOutput = 0; zOutput < outputDepth; zOutput++)
+        {
+            for (unsigned int xOutput = 0; xOutput < outputWidth; xOutput++)
+            {
+                for (unsigned int yOutput = 0; yOutput < outputHeight; yOutput++)
+                {
+                    for (unsigned int cOutput = 0; cOutput < outChannels; cOutput++)
+                    {
+                        // This loop goes over each output element.
+                        float sum = 0.0f;
+
+                        // Loop over each input channel.
+                        for (unsigned int zFilter = 0; zFilter < filterDepth; zFilter++)
+                        {
+                            for (unsigned int yFilter = 0; yFilter < filterHeight; yFilter++)
+                            {
+                                for (unsigned int xFilter = 0; xFilter < filterWidth; xFilter++)
+                                {
+                                    for (unsigned int cInput = 0; cInput < inChannels; cInput++)
+                                    {
+                                        // This loop goes over each input element for each output element.
+                                        unsigned int filterIndex = 0;
+
+                                        // Conv3d weights layout: [D,H,W,I,O]
+                                        // Keep this implementation, as using DataLayoutIndexed::GetIndex
+                                        // causes large performance regression.
+                                        filterIndex = zFilter * filterHeight * filterWidth * inChannels * outChannels +
+                                                      yFilter * filterWidth * inChannels * outChannels +
+                                                      xFilter * inChannels * outChannels +
+                                                      cInput * outChannels +
+                                                      cOutput;
+
+                                        unsigned int yInput = yOutput * yStride + yFilter * yDilation;
+                                        unsigned int xInput = xOutput * xStride + xFilter * xDilation;
+                                        unsigned int zInput = zOutput * zStride + zFilter * zDilation;
+
+                                        float inputValue;
+
+                                        // Check if we're in the padding.
+                                        if (yInput < paddingTop || yInput >= inputHeight + paddingTop ||
+                                            xInput < paddingLeft || xInput >= inputWidth + paddingLeft ||
+                                            zInput < paddingFront || zInput >= inputDepth + paddingFront)
+                                        {
+                                            inputValue = 0.0f;
+                                        }
+                                        else
+                                        {
+                                            unsigned int inputIndex = 0;
+
+                                            // Keep this implementation, as using DataLayoutIndexed::GetIndex
+                                            // causes large performance regression.
+                                            inputIndex = batchIdx * inputDepth * inputHeight * inputWidth * inChannels +
+                                                         (zInput-paddingFront) * inputHeight * inputWidth * inChannels +
+                                                         (yInput-paddingTop) * inputWidth * inChannels +
+                                                         (xInput-paddingLeft) * inChannels +
+                                                         cInput;
+
+                                            inputValue = inputVec[inputIndex];
+                                        }
+
+                                        sum += filterVec[filterIndex] * inputValue;
+                                    }
+                                }
+                            }
+                        }
+
+                        if (biasEnabled)
+                        {
+                            sum += biasVec[cOutput];
+                        }
+
+                        unsigned int outIdx = batchIdx * outputDepth * outputHeight * outputWidth * outChannels +
+                                              zOutput * outputHeight * outputWidth * outChannels +
+                                              yOutput * outputWidth * outChannels +
+                                              xOutput * outChannels +
+                                              cOutput;
+
+                        rOutputEncoder[outIdx];
+                        rOutputEncoder.Set(sum);
+                    }
+                }
+            }
+        }
+    }
+}
+
+} // namespace armnn
diff --git a/src/backends/reference/workloads/Conv3dImpl.hpp b/src/backends/reference/workloads/Conv3dImpl.hpp
new file mode 100644
index 0000000..5cf2ed9
--- /dev/null
+++ b/src/backends/reference/workloads/Conv3dImpl.hpp
@@ -0,0 +1,38 @@
+//
+// Copyright © 2021 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include "BaseIterator.hpp"
+#include "Decoders.hpp"
+#include "Encoders.hpp"
+
+#include <armnn/Tensor.hpp>
+
+#include <armnnUtils/DataLayoutIndexed.hpp>
+
+namespace armnn
+{
+
+void Convolve3d(const TensorShape& rInputShape,
+                Decoder<float>& rInputDecoder,
+                const TensorShape& rOutputShape,
+                Encoder<float>& rOutputEncoder,
+                const TensorShape& rFilterShape,
+                Decoder<float>& rFilterDecoder,
+                bool biasEnabled,
+                Decoder<float>* pBiasDecoder,
+                DataLayout dataLayout,
+                unsigned int paddingTop,
+                unsigned int paddingLeft,
+                unsigned int paddingFront,
+                unsigned int xStride,
+                unsigned int yStride,
+                unsigned int zStride,
+                unsigned int xDilation,
+                unsigned int yDilation,
+                unsigned int zDilation);
+
+} //namespace armnn
diff --git a/src/backends/reference/workloads/RefConvolution3dWorkload.cpp b/src/backends/reference/workloads/RefConvolution3dWorkload.cpp
new file mode 100644
index 0000000..ea425da
--- /dev/null
+++ b/src/backends/reference/workloads/RefConvolution3dWorkload.cpp
@@ -0,0 +1,76 @@
+//
+// Copyright © 2021 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "RefConvolution3dWorkload.hpp"
+
+#include "Conv3dImpl.hpp"
+#include "RefWorkloadUtils.hpp"
+
+#include "Profiling.hpp"
+
+namespace armnn
+{
+RefConvolution3dWorkload::RefConvolution3dWorkload(
+    const Convolution3dQueueDescriptor& descriptor, const WorkloadInfo& info)
+    : BaseWorkload<Convolution3dQueueDescriptor>(descriptor, info)
+{
+    WorkloadInfo detailsInfo;
+    detailsInfo.m_InputTensorInfos = info.m_InputTensorInfos;
+    detailsInfo.m_OutputTensorInfos = info.m_OutputTensorInfos;
+    detailsInfo.m_WeightsTensorInfo = armnn::Optional<armnn::TensorInfo>(descriptor.m_Weight->GetTensorInfo());
+    if (descriptor.m_Parameters.m_BiasEnabled)
+    {
+        detailsInfo.m_BiasTensorInfo = armnn::Optional<armnn::TensorInfo>(descriptor.m_Bias->GetTensorInfo());
+    }
+
+    // Report Profiling Details
+    ARMNN_REPORT_PROFILING_WORKLOAD_DESC("RefConvolution3dWorkload_Construct",
+                                         descriptor.m_Parameters,
+                                         detailsInfo,
+                                         this->GetGuid());
+
+    m_Weight = std::make_unique<ScopedTensorHandle>(*( descriptor.m_Weight ));
+    const TensorInfo& rFilterInfo = m_Weight->GetTensorInfo();
+
+    m_FilterShape = rFilterInfo.GetShape();
+    m_FilterDecoder = MakeDecoder<float>(rFilterInfo, m_Weight.get()->Map(true));
+
+    if ( descriptor.m_Parameters.m_BiasEnabled )
+    {
+        m_Bias = std::make_unique<ScopedTensorHandle>(*( descriptor.m_Bias ));
+        const TensorInfo& biasInfo = m_Bias->GetTensorInfo();
+        m_BiasDecoder = MakeDecoder<float>(biasInfo, m_Bias->Map(true));
+    }
+}
+
+void RefConvolution3dWorkload::Execute() const
+{
+    Execute(m_Data.m_Inputs, m_Data.m_Outputs);
+}
+
+void RefConvolution3dWorkload::ExecuteAsync(WorkingMemDescriptor& workingMemDescriptor)
+{
+    Execute(workingMemDescriptor.m_Inputs, workingMemDescriptor.m_Outputs);
+}
+
+void RefConvolution3dWorkload::Execute(std::vector<ITensorHandle*> inputs, std::vector<ITensorHandle*> outputs) const
+{
+    ARMNN_SCOPED_PROFILING_EVENT_GUID(Compute::CpuRef, "RefConvolution3dWorkload_Execute", this->GetGuid());
+
+    std::unique_ptr<Decoder<float>> inputDecoder = MakeDecoder<float>(GetTensorInfo(inputs[0]), inputs[0]->Map());
+    std::unique_ptr<Encoder<float>> outputEncoder = MakeEncoder<float>(GetTensorInfo(outputs[0]), outputs[0]->Map());
+
+    const TensorShape& inputShape = GetTensorInfo(inputs[0]).GetShape();
+    const TensorShape& outputShape = GetTensorInfo(outputs[0]).GetShape();
+
+    Convolve3d(inputShape, *inputDecoder, outputShape, *outputEncoder, m_FilterShape,
+               *m_FilterDecoder, m_Data.m_Parameters.m_BiasEnabled, m_BiasDecoder.get(),
+               m_Data.m_Parameters.m_DataLayout,
+               m_Data.m_Parameters.m_PadTop, m_Data.m_Parameters.m_PadLeft, m_Data.m_Parameters.m_PadFront,
+               m_Data.m_Parameters.m_StrideX, m_Data.m_Parameters.m_StrideY, m_Data.m_Parameters.m_StrideZ,
+               m_Data.m_Parameters.m_DilationX, m_Data.m_Parameters.m_DilationY, m_Data.m_Parameters.m_DilationZ);
+}
+
+} //namespace armnn
diff --git a/src/backends/reference/workloads/RefConvolution3dWorkload.hpp b/src/backends/reference/workloads/RefConvolution3dWorkload.hpp
new file mode 100644
index 0000000..0373a8b
--- /dev/null
+++ b/src/backends/reference/workloads/RefConvolution3dWorkload.hpp
@@ -0,0 +1,38 @@
+//
+// Copyright © 2021 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include <backendsCommon/Workload.hpp>
+#include <backendsCommon/WorkloadData.hpp>
+#include "Decoders.hpp"
+#include "Encoders.hpp"
+
+namespace armnn
+{
+
+class RefConvolution3dWorkload : public BaseWorkload<Convolution3dQueueDescriptor>
+{
+public:
+    explicit RefConvolution3dWorkload(const Convolution3dQueueDescriptor& descriptor,
+                                      const WorkloadInfo& info);
+
+
+    void Execute() const override;
+    void ExecuteAsync(WorkingMemDescriptor& workingMemDescriptor)  override;
+
+private:
+    void Execute(std::vector<ITensorHandle*> inputs, std::vector<ITensorHandle*> outputs) const;
+    std::unique_ptr<ScopedTensorHandle> m_Weight;
+    std::unique_ptr<ScopedTensorHandle> m_Bias;
+
+    std::unique_ptr<Decoder<float>> m_FilterDecoder;
+    std::unique_ptr<Decoder<float>> m_BiasDecoder;
+
+    TensorShape m_FilterShape;
+};
+
+} //namespace armnn
+
diff --git a/src/backends/reference/workloads/RefWorkloads.hpp b/src/backends/reference/workloads/RefWorkloads.hpp
index 1cf84ee..ed3aa90 100644
--- a/src/backends/reference/workloads/RefWorkloads.hpp
+++ b/src/backends/reference/workloads/RefWorkloads.hpp
@@ -22,6 +22,7 @@
 #include "RefChannelShuffleWorkload.hpp"
 #include "RefComparisonWorkload.hpp"
 #include "RefConvolution2dWorkload.hpp"
+#include "RefConvolution3dWorkload.hpp"
 #include "RefConstantWorkload.hpp"
 #include "RefConcatWorkload.hpp"
 #include "RefConvertBf16ToFp32Workload.hpp"