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/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");