Main Compliance FP16 support - generate and verify.

FP16 support for all existing operators for compliance:
* DOT_PRODUCT
* ULP
* EXACT
* ABS_ERROR

Signed-off-by: Jeremy Johnson <jeremy.johnson@arm.com>
Change-Id: I8d25448a793375b53880da3787d8f839767f02cf
diff --git a/reference_model/src/generate/generate_dot_product.cc b/reference_model/src/generate/generate_dot_product.cc
index c8a2b13..130b41d 100644
--- a/reference_model/src/generate/generate_dot_product.cc
+++ b/reference_model/src/generate/generate_dot_product.cc
@@ -13,6 +13,7 @@
 //    limitations under the License.
 
 #include "generate_dot_product.h"
+#include "half.hpp"
 
 namespace
 {
@@ -20,34 +21,34 @@
 //                              MatMul                                       //
 //---------------------------------------------------------------------------//
 
+template <typename DataType>
 void generateMatMulA(const TosaReference::GenerateConfig& cfg,
                      TosaReference::IDotProductGenerator& generator,
-                     void* data,
+                     DataType* data,
                      size_t size)
 {
-    float* a         = reinterpret_cast<float*>(data);
     const uint32_t T = cfg.shape[0] * cfg.shape[1] * cfg.shape[2];
     const uint32_t C = cfg.shape[2];
 
     for (uint32_t t = 0; t < T; ++t)
     {
-        a[t] = generator(t % C);    // k = c
+        data[t] = static_cast<DataType>(generator(t % C));    // k = c
     }
 }
 
+template <typename DataType>
 void generateMatMulB(const TosaReference::GenerateConfig& cfg,
                      TosaReference::IDotProductGenerator& generator,
-                     void* data,
+                     DataType* data,
                      size_t size)
 {
-    float* b         = reinterpret_cast<float*>(data);
     const uint32_t T = cfg.shape[0] * cfg.shape[1] * cfg.shape[2];
     const uint32_t C = cfg.shape[1];
     const uint32_t W = cfg.shape[2];
 
     for (uint32_t t = 0; t < T; ++t)
     {
-        b[t] = generator((t / W) % C);    // k = c
+        data[t] = static_cast<DataType>(generator((t / W) % C));    // k = c
     }
 }
 
@@ -56,11 +57,6 @@
                     void* data,
                     size_t size)
 {
-    if (cfg.dataType != DType::DType_FP32)
-    {
-        WARNING("[Generator][DP][MatMul] Only supports FP32.");
-        return false;
-    }
     if (cfg.shape.size() != 3)
     {
         WARNING("[Generator][DP][MatMul] Tensor shape expected 3 dimensions.");
@@ -72,7 +68,24 @@
         return false;
     }
 
-    (cfg.inputPos == 0) ? generateMatMulA(cfg, generator, data, size) : generateMatMulB(cfg, generator, data, size);
+    switch (cfg.dataType)
+    {
+        case DType::DType_FP32: {
+            float* outData = reinterpret_cast<float*>(data);
+            (cfg.inputPos == 0) ? generateMatMulA(cfg, generator, outData, size)
+                                : generateMatMulB(cfg, generator, outData, size);
+            break;
+        }
+        case DType::DType_FP16: {
+            half_float::half* outData = reinterpret_cast<half_float::half*>(data);
+            (cfg.inputPos == 0) ? generateMatMulA(cfg, generator, outData, size)
+                                : generateMatMulB(cfg, generator, outData, size);
+            break;
+        }
+        default:
+            WARNING("[Generator][DP][MatMul] Only supports FP32 or FP16.");
+            return false;
+    }
 
     return true;
 }
@@ -80,9 +93,10 @@
 //                              Conv2D                                       //
 //---------------------------------------------------------------------------//
 
+template <typename DataType>
 bool generateConv2DInput(const TosaReference::GenerateConfig& cfg,
                          TosaReference::IDotProductGenerator& generator,
-                         void* data,
+                         DataType* data,
                          size_t size)
 {
     if (cfg.dotProductInfo.kernel.size() != 2 || cfg.dotProductInfo.kernel[0] <= 0 || cfg.dotProductInfo.kernel[1] <= 0)
@@ -96,7 +110,6 @@
         return false;
     }
 
-    float* input      = reinterpret_cast<float*>(data);
     const int64_t T   = TosaReference::numElementsFromShape(cfg.shape);
     const uint32_t IH = cfg.shape[1];
     const uint32_t IW = cfg.shape[2];
@@ -111,14 +124,15 @@
         uint32_t iy = ((t / IC) / IW) % IH;
         uint32_t k  = ((iy % KH) * KW + (ix % KW)) * IC + ic;
 
-        input[t] = generator(k);
+        data[t] = static_cast<DataType>(generator(k));
     }
     return true;
 }
 
+template <typename DataType>
 bool generateConv2DWeight(const TosaReference::GenerateConfig& cfg,
                           TosaReference::IDotProductGenerator& generator,
-                          void* data,
+                          DataType* data,
                           size_t size)
 {
     if (cfg.shape.size() != 4)
@@ -127,7 +141,6 @@
         return false;
     }
 
-    float* weight     = reinterpret_cast<float*>(data);
     const int64_t T   = TosaReference::numElementsFromShape(cfg.shape);
     const uint32_t KH = cfg.shape[1];
     const uint32_t KW = cfg.shape[2];
@@ -140,14 +153,15 @@
         uint32_t ky = ((t / IC) / KW) % KH;
         uint32_t k  = (ky + KW * kx) * IC + ic;
 
-        weight[t] = generator(k);
+        data[t] = static_cast<DataType>(generator(k));
     }
     return true;
 }
 
+template <typename DataType>
 bool generateConv2DBias(const TosaReference::GenerateConfig& cfg,
                         TosaReference::IDotProductGenerator& generator,
-                        void* data,
+                        DataType* data,
                         size_t size)
 {
     if (cfg.shape.size() != 1)
@@ -156,12 +170,11 @@
         return false;
     }
 
-    float* bias      = reinterpret_cast<float*>(data);
     const uint32_t T = cfg.shape[0];
 
     for (uint32_t t = 0; t < T; ++t)
     {
-        bias[t] = generator(2);
+        data[t] = static_cast<DataType>(generator(2));
     }
     return true;
 }
@@ -171,21 +184,42 @@
                     void* data,
                     size_t size)
 {
-    if (cfg.dataType != DType::DType_FP32)
+    switch (cfg.dataType)
     {
-        WARNING("[Generator][DP][Conv2D] Only supports FP32.");
-        return false;
-    }
-    switch (cfg.inputPos)
-    {
-        case 0:
-            return generateConv2DInput(cfg, generator, data, size);
-        case 1:
-            return generateConv2DWeight(cfg, generator, data, size);
-        case 2:
-            return generateConv2DBias(cfg, generator, data, size);
+        case DType::DType_FP32: {
+            float* outData = reinterpret_cast<float*>(data);
+            switch (cfg.inputPos)
+            {
+                case 0:
+                    return generateConv2DInput(cfg, generator, outData, size);
+                case 1:
+                    return generateConv2DWeight(cfg, generator, outData, size);
+                case 2:
+                    return generateConv2DBias(cfg, generator, outData, size);
+                default:
+                    WARNING("[Generator][DP][Conv2D] Invalid input tensor slot position to operator.");
+                    return false;
+            }
+            break;
+        }
+        case DType::DType_FP16: {
+            half_float::half* outData = reinterpret_cast<half_float::half*>(data);
+            switch (cfg.inputPos)
+            {
+                case 0:
+                    return generateConv2DInput(cfg, generator, outData, size);
+                case 1:
+                    return generateConv2DWeight(cfg, generator, outData, size);
+                case 2:
+                    return generateConv2DBias(cfg, generator, outData, size);
+                default:
+                    WARNING("[Generator][DP][Conv2D] Invalid input tensor slot position to operator.");
+                    return false;
+            }
+            break;
+        }
         default:
-            WARNING("[Generator][DP][Conv2D] Invalid input tensor slot position to operator.");
+            WARNING("[Generator][DP][Conv2D] Only supports FP32 or FP16.");
             return false;
     }
 }
@@ -193,28 +227,12 @@
 //                              Reduce Sum                                   //
 //---------------------------------------------------------------------------//
 
-bool generateReduceSum(const TosaReference::GenerateConfig& cfg,
-                       TosaReference::IDotProductGenerator& generator,
-                       void* data,
-                       size_t size)
+template <typename DataType>
+void generateReduceSumData(const TosaReference::GenerateConfig& cfg,
+                           TosaReference::IDotProductGenerator& generator,
+                           DataType* data,
+                           size_t size)
 {
-    if (cfg.dataType != DType::DType_FP32)
-    {
-        WARNING("[Generator][DP][ReduceSum] Only supports FP32.");
-        return false;
-    }
-    if (cfg.inputPos != 0)
-    {
-        WARNING("[Generator][DP][ReduceSum] Invalid input tensor slot position to operator.");
-        return false;
-    }
-    if (cfg.dotProductInfo.axis < 0 || static_cast<size_t>(cfg.dotProductInfo.axis) >= cfg.shape.size())
-    {
-        WARNING("[Generator][DP][ReduceSum] Invalid axis %d.", cfg.dotProductInfo.axis);
-        return false;
-    }
-
-    float* input        = reinterpret_cast<float*>(data);
     const int64_t T     = TosaReference::numElementsFromShape(cfg.shape);
     const uint32_t axis = cfg.dotProductInfo.axis;
 
@@ -227,17 +245,53 @@
         }
         k = k % cfg.shape[axis];
 
-        input[t] = generator(static_cast<int32_t>(k));
+        data[t] = static_cast<DataType>(generator(static_cast<int32_t>(k)));
     }
+}
+
+bool generateReduceSum(const TosaReference::GenerateConfig& cfg,
+                       TosaReference::IDotProductGenerator& generator,
+                       void* data,
+                       size_t size)
+{
+    if (cfg.inputPos != 0)
+    {
+        WARNING("[Generator][DP][ReduceSum] Invalid input tensor slot position to operator.");
+        return false;
+    }
+    if (cfg.dotProductInfo.axis < 0 || static_cast<size_t>(cfg.dotProductInfo.axis) >= cfg.shape.size())
+    {
+        WARNING("[Generator][DP][ReduceSum] Invalid axis %d.", cfg.dotProductInfo.axis);
+        return false;
+    }
+
+    switch (cfg.dataType)
+    {
+        case DType::DType_FP32: {
+            float* outData = reinterpret_cast<float*>(data);
+            generateReduceSumData(cfg, generator, outData, size);
+            break;
+        }
+        case DType::DType_FP16: {
+            half_float::half* outData = reinterpret_cast<half_float::half*>(data);
+            generateReduceSumData(cfg, generator, outData, size);
+            break;
+        }
+        default:
+            WARNING("[Generator][DP][ReduceSum] Only supports FP32 or FP16.");
+            return false;
+    }
+
     return true;
 }
 //---------------------------------------------------------------------------//
 //                              Fully Connected                              //
 //---------------------------------------------------------------------------//
 
+template <typename DataType>
 bool generateFullyConnectedInput(const TosaReference::GenerateConfig& cfg,
                                  TosaReference::IDotProductGenerator& generator,
-                                 void* data,
+                                 DataType* data,
                                  size_t size)
 {
     if (cfg.shape.size() != 2)
@@ -246,7 +300,6 @@
         return false;
     }
 
-    float* input      = reinterpret_cast<float*>(data);
     const int64_t T   = TosaReference::numElementsFromShape(cfg.shape);
     const uint32_t IC = cfg.shape[1];
 
@@ -254,14 +307,15 @@
     {
         uint32_t k = t % IC;
 
-        input[t] = generator(k);
+        data[t] = static_cast<DataType>(generator(k));
     }
     return true;
 }
 
+template <typename DataType>
 bool generateFullyConnectedWeight(const TosaReference::GenerateConfig& cfg,
                                   TosaReference::IDotProductGenerator& generator,
-                                  void* data,
+                                  DataType* data,
                                   size_t size)
 {
     if (cfg.shape.size() != 2)
@@ -270,7 +324,6 @@
         return false;
     }
 
-    float* weight     = reinterpret_cast<float*>(data);
     const int64_t T   = TosaReference::numElementsFromShape(cfg.shape);
     const uint32_t IC = cfg.shape[1];
 
@@ -278,14 +331,15 @@
     {
         uint32_t k = t % IC;
 
-        weight[t] = generator(k);
+        data[t] = static_cast<DataType>(generator(k));
     }
     return true;
 }
 
+template <typename DataType>
 bool generateFullyConnectedBias(const TosaReference::GenerateConfig& cfg,
                                 TosaReference::IDotProductGenerator& generator,
-                                void* data,
+                                DataType* data,
                                 size_t size)
 {
     if (cfg.shape.size() != 1)
@@ -294,12 +348,11 @@
         return false;
     }
 
-    float* bias      = reinterpret_cast<float*>(data);
     const uint32_t T = cfg.shape[0];
 
     for (uint32_t t = 0; t < T; ++t)
     {
-        bias[t] = generator(2);
+        data[t] = static_cast<DataType>(generator(2));
     }
     return true;
 }
@@ -309,21 +362,42 @@
                             void* data,
                             size_t size)
 {
-    if (cfg.dataType != DType::DType_FP32)
+    switch (cfg.dataType)
     {
-        WARNING("[Generator][DP][FullyConnected] Only supports FP32.");
-        return false;
-    }
-    switch (cfg.inputPos)
-    {
-        case 0:
-            return generateFullyConnectedInput(cfg, generator, data, size);
-        case 1:
-            return generateFullyConnectedWeight(cfg, generator, data, size);
-        case 2:
-            return generateFullyConnectedBias(cfg, generator, data, size);
+        case DType::DType_FP32: {
+            float* outData = reinterpret_cast<float*>(data);
+            switch (cfg.inputPos)
+            {
+                case 0:
+                    return generateFullyConnectedInput(cfg, generator, outData, size);
+                case 1:
+                    return generateFullyConnectedWeight(cfg, generator, outData, size);
+                case 2:
+                    return generateFullyConnectedBias(cfg, generator, outData, size);
+                default:
+                    WARNING("[Generator][DP][FullyConnected] Invalid input tensor slot position to operator.");
+                    return false;
+            }
+            break;
+        }
+        case DType::DType_FP16: {
+            half_float::half* outData = reinterpret_cast<half_float::half*>(data);
+            switch (cfg.inputPos)
+            {
+                case 0:
+                    return generateFullyConnectedInput(cfg, generator, outData, size);
+                case 1:
+                    return generateFullyConnectedWeight(cfg, generator, outData, size);
+                case 2:
+                    return generateFullyConnectedBias(cfg, generator, outData, size);
+                default:
+                    WARNING("[Generator][DP][FullyConnected] Invalid input tensor slot position to operator.");
+                    return false;
+            }
+            break;
+        }
         default:
-            WARNING("[Generator][DP][FullyConnected] Invalid input tensor slot position to operator.");
+            WARNING("[Generator][DP][FullyConnected] Only supports FP32 or FP16.");
             return false;
     }
 }
@@ -331,16 +405,35 @@
 //                              Avg Pool 2D                                   //
 //---------------------------------------------------------------------------//
 
+template <typename DataType>
+void generateAvgPool2DData(const TosaReference::GenerateConfig& cfg,
+                           TosaReference::IDotProductGenerator& generator,
+                           DataType* data,
+                           size_t size)
+{
+    const int64_t T   = TosaReference::numElementsFromShape(cfg.shape);
+    const uint32_t IH = cfg.shape[1];
+    const uint32_t IW = cfg.shape[2];
+    const uint32_t C  = cfg.shape[3];
+    const uint32_t KY = cfg.dotProductInfo.kernel[0];
+    const uint32_t KX = cfg.dotProductInfo.kernel[1];
+
+    for (int64_t t = 0; t < T; ++t)
+    {
+        uint32_t c  = t % C;
+        uint32_t ix = (t / C) % IW;
+        uint32_t iy = ((t / C) / IW) % IH;
+        uint32_t k  = ((iy % KY) * KX + (ix % KX)) * C + c;
+
+        data[t] = static_cast<DataType>(generator(k));
+    }
+}
+
 bool generateAvgPool2D(const TosaReference::GenerateConfig& cfg,
                        TosaReference::IDotProductGenerator& generator,
                        void* data,
                        size_t size)
 {
-    if (cfg.dataType != DType::DType_FP32)
-    {
-        WARNING("[Generator][DP][AvgPool2D] Only supports FP32.");
-        return false;
-    }
     if (cfg.inputPos != 0)
     {
         WARNING("[Generator][DP][AvgPool2D] Invalid input tensor slot position to operator.");
@@ -357,23 +450,23 @@
         return false;
     }
 
-    float* input      = reinterpret_cast<float*>(data);
-    const int64_t T   = TosaReference::numElementsFromShape(cfg.shape);
-    const uint32_t IH = cfg.shape[1];
-    const uint32_t IW = cfg.shape[2];
-    const uint32_t C  = cfg.shape[3];
-    const uint32_t KY = cfg.dotProductInfo.kernel[0];
-    const uint32_t KX = cfg.dotProductInfo.kernel[1];
-
-    for (int64_t t = 0; t < T; ++t)
+    switch (cfg.dataType)
     {
-        uint32_t c  = t % C;
-        uint32_t ix = (t / C) % IW;
-        uint32_t iy = ((t / C) / IW) % IH;
-        uint32_t k  = ((iy % KY) * KX + (ix % KX)) * C + c;
-
-        input[t] = generator(k);
+        case DType::DType_FP32: {
+            float* outData = reinterpret_cast<float*>(data);
+            generateAvgPool2DData(cfg, generator, outData, size);
+            break;
+        }
+        case DType::DType_FP16: {
+            half_float::half* outData = reinterpret_cast<half_float::half*>(data);
+            generateAvgPool2DData(cfg, generator, outData, size);
+            break;
+        }
+        default:
+            WARNING("[Generator][DP][AvgPool2D] Only supports FP32 or FP16.");
+            return false;
     }
+
     return true;
 }
 }    // namespace
diff --git a/reference_model/src/generate/generate_pseudo_random.cc b/reference_model/src/generate/generate_pseudo_random.cc
index b51424d..b62c38f 100644
--- a/reference_model/src/generate/generate_pseudo_random.cc
+++ b/reference_model/src/generate/generate_pseudo_random.cc
@@ -13,6 +13,7 @@
 //    limitations under the License.
 #include "generate.h"
 #include "generate_utils.h"
+#include "half.hpp"
 
 #include <array>
 #include <iterator>
@@ -88,7 +89,8 @@
     bool _useUniform;
 };
 
-bool generateFP32(const TosaReference::GenerateConfig& cfg, void* data, size_t size)
+template <typename DataType>
+bool generateFP(const TosaReference::GenerateConfig& cfg, DataType* data, size_t size)
 {
     const TosaReference::PseudoRandomInfo& prinfo = cfg.pseudoRandomInfo;
 
@@ -106,21 +108,20 @@
         generator = new PseudoRandomGeneratorFloat<float>(prinfo.rngSeed);
     }
 
-    float* a     = reinterpret_cast<float*>(data);
     const auto T = TosaReference::numElementsFromShape(cfg.shape);
     const bool comparisonOp =
         (cfg.opType == Op::Op_EQUAL) || (cfg.opType == Op::Op_GREATER_EQUAL) || (cfg.opType == Op::Op_GREATER);
     for (auto t = 0; t < T; ++t)
     {
-        a[t] = generator->getRandomFloat();
+        data[t] = static_cast<DataType>(generator->getRandomFloat());
         if (comparisonOp && (t % 4 == 0))
         {
             // Set every 4th value to 0 to enable better comparison testing
-            a[t] = 0.f;
+            data[t] = static_cast<DataType>(0.f);
         }
         else if (roundMode)
         {
-            a[t] = std::roundf(a[t]);
+            data[t] = static_cast<DataType>(std::roundf(data[t]));
         }
     }
     return true;
@@ -146,8 +147,14 @@
 
     switch (cfg.dataType)
     {
-        case DType::DType_FP32:
-            return generateFP32(cfg, data, size);
+        case DType::DType_FP32: {
+            float* outData = reinterpret_cast<float*>(data);
+            return generateFP(cfg, outData, size);
+        }
+        case DType::DType_FP16: {
+            half_float::half* outData = reinterpret_cast<half_float::half*>(data);
+            return generateFP(cfg, outData, size);
+        }
         default:
             WARNING("[Generator][PR] Unsupported type.");
             return false;
diff --git a/reference_model/src/verify/verify_abs_error.cc b/reference_model/src/verify/verify_abs_error.cc
index b43da08..5aaa0ad 100644
--- a/reference_model/src/verify/verify_abs_error.cc
+++ b/reference_model/src/verify/verify_abs_error.cc
@@ -18,6 +18,7 @@
 #include <type_traits>
 #include <utility>
 
+#include "half.hpp"
 #include "verifiers.h"
 
 namespace TosaReference
@@ -25,14 +26,15 @@
 
 namespace
 {
-bool validateData(const double* ref, const double* bnd, const float* imp, const std::vector<int32_t>& shape)
+template <typename OutDtype>
+bool validateData(const double* ref, const double* bnd, const OutDtype* imp, const std::vector<int32_t>& shape)
 {
     const size_t T = static_cast<size_t>(numElements(shape));
     TOSA_REF_REQUIRE(T > 0, "[AE] Invalid shape for reference tensor");
 
     for (size_t i = 0; i < T; ++i)
     {
-        double errBound = std::abs(ref[i]) * exp2(-AccPrecision<float>::normal_frac) * bnd[i];
+        double errBound = std::abs(ref[i]) * exp2(-AccPrecision<OutDtype>::normal_frac) * bnd[i];
         bool valid      = tosaCheckFloatBound(imp[i], ref[i], errBound);
         if (!valid)
         {
@@ -60,7 +62,12 @@
     switch (imp->data_type)
     {
         case tosa_datatype_fp32_t: {
-            const float* impData = reinterpret_cast<const float*>(imp->data);
+            const auto* impData = reinterpret_cast<const float*>(imp->data);
+            TOSA_REF_REQUIRE(impData != nullptr, "[AE] Missing data for implementation");
+            return validateData(refData, refBndData, impData, refShape);
+        }
+        case tosa_datatype_fp16_t: {
+            const auto* impData = reinterpret_cast<const half_float::half*>(imp->data);
             TOSA_REF_REQUIRE(impData != nullptr, "[AE] Missing data for implementation");
             return validateData(refData, refBndData, impData, refShape);
         }
diff --git a/reference_model/src/verify/verify_dot_product.cc b/reference_model/src/verify/verify_dot_product.cc
index 15de427..a036cba 100644
--- a/reference_model/src/verify/verify_dot_product.cc
+++ b/reference_model/src/verify/verify_dot_product.cc
@@ -13,6 +13,7 @@
 //    limitations under the License.
 
 #include "func_debug.h"
+#include "half.hpp"
 #include "verifiers.h"
 
 #include <cmath>
@@ -25,13 +26,19 @@
 namespace
 {
 // Generic element validation function
-template <typename AccType, typename std::enable_if_t<std::is_floating_point_v<AccType>, int> = 0>
+template <typename AccType>
 std::optional<double> validateElement(size_t index, double ref, double bnd, AccType imp, size_t KS)
 {
     double err    = 0.0;
     bool is_valid = true;
 
-    if (bnd == 0.0)
+    if (std::isinf(static_cast<AccType>(bnd)))
+    {
+        // dot product can overflow and there is no accuracy limit
+        is_valid = true;
+        err      = 0.0;
+    }
+    else if (bnd == 0.0)
     {
         is_valid = (ref == 0.0) && (imp == 0.0);
         if (!is_valid)
@@ -40,12 +47,6 @@
         }
         err = 0.0;
     }
-    else if (std::isinf(static_cast<AccType>(bnd)))
-    {
-        // dot product can overflow and there is no accuracy limit
-        is_valid = true;
-        err      = 0.0;
-    }
     else
     {
         // 0.0 < bnd < infinity
@@ -64,7 +65,7 @@
 }
 
 // Generic data validation function
-template <typename AccType, typename std::enable_if_t<std::is_floating_point_v<AccType>, int> = 0>
+template <typename AccType>
 bool validateData(const double* ref, const double* bnd, const AccType* imp, size_t T, const DotProductVerifyInfo& cfg)
 {
     const int32_t S = cfg.s;
@@ -121,6 +122,12 @@
             return validateData(refData, refBndData, impData, static_cast<size_t>(T), dpInfo);
             break;
         }
+        case tosa_datatype_fp16_t: {
+            const half_float::half* impData = reinterpret_cast<const half_float::half*>(imp->data);
+            TOSA_REF_REQUIRE(impData != nullptr, "[DP] Missing data for implementation");
+            return validateData(refData, refBndData, impData, static_cast<size_t>(T), dpInfo);
+            break;
+        }
         default: {
             WARNING("[Verifier][DP] Data-type not supported.");
             break;
diff --git a/reference_model/src/verify/verify_exact.cc b/reference_model/src/verify/verify_exact.cc
index 36b4ec9..971df9c 100644
--- a/reference_model/src/verify/verify_exact.cc
+++ b/reference_model/src/verify/verify_exact.cc
@@ -13,12 +13,14 @@
 //    limitations under the License.
 
 #include "func_debug.h"
+#include "half.hpp"
 #include "verifiers.h"
 #include <cmath>
 
 namespace
 {
-bool exact_fp32(const double& referenceValue, const float& implementationValue)
+template <typename OutDtype>
+bool exact_fp(const double& referenceValue, const OutDtype& implementationValue)
 {
     return std::isnan(referenceValue) ? std::isnan(implementationValue) : (referenceValue == implementationValue);
 }
@@ -38,16 +40,24 @@
         numElements(std::vector<int32_t>(referenceTensor->shape, referenceTensor->shape + referenceTensor->num_dims));
     TOSA_REF_REQUIRE(elementCount > 0, "[E] Invalid shape for reference tensor");
 
+    TOSA_REF_REQUIRE(referenceTensor->data_type == tosa_datatype_fp64_t, "[E] Reference tensor is not fp64");
+    const auto* refData = reinterpret_cast<const double*>(referenceTensor->data);
+    TOSA_REF_REQUIRE(refData != nullptr, "[E] Missing data for reference");
+
     switch (implementationTensor->data_type)
     {
         case tosa_datatype_fp32_t: {
-            TOSA_REF_REQUIRE(referenceTensor->data_type == tosa_datatype_fp64_t, "[E] Reference tensor is not fp64");
-            const auto* refData = reinterpret_cast<const double*>(referenceTensor->data);
-            TOSA_REF_REQUIRE(refData != nullptr, "[E] Missing data for reference");
             const auto* impData = reinterpret_cast<const float*>(implementationTensor->data);
             TOSA_REF_REQUIRE(impData != nullptr, "[E] Missing data for implementation");
             auto result = std::equal(refData, std::next(refData, elementCount), impData,
-                                     std::next(impData, elementCount), exact_fp32);
+                                     std::next(impData, elementCount), exact_fp<float>);
+            return result;
+        }
+        case tosa_datatype_fp16_t: {
+            const auto* impData = reinterpret_cast<const half_float::half*>(implementationTensor->data);
+            TOSA_REF_REQUIRE(impData != nullptr, "[E] Missing data for implementation");
+            auto result = std::equal(refData, std::next(refData, elementCount), impData,
+                                     std::next(impData, elementCount), exact_fp<half_float::half>);
             return result;
         }
         default:
diff --git a/reference_model/src/verify/verify_ulp.cc b/reference_model/src/verify/verify_ulp.cc
index 6e78b96..1b38fe6 100644
--- a/reference_model/src/verify/verify_ulp.cc
+++ b/reference_model/src/verify/verify_ulp.cc
@@ -18,6 +18,7 @@
 #include <type_traits>
 #include <utility>
 
+#include "half.hpp"
 #include "verifiers.h"
 
 namespace TosaReference
@@ -25,7 +26,8 @@
 
 namespace
 {
-bool tosaCheckULP(float testValue, double referenceValue, double ulpNum)
+template <typename OutType>
+bool tosaCheckULP(OutType testValue, double referenceValue, double ulpNum)
 {
     double errorBound = 0.0;
     if (std::isfinite(referenceValue) && std::abs(referenceValue) != 0.0)
@@ -35,10 +37,10 @@
 
         // Work out the values magnitude - by raising 2 to the power of the
         // exponent and taking the normalized minimum for denormal values
-        const double referencePower2 = std::max(exp2(referenceExponent), AccPrecision<float>::normal_min);
+        const double referencePower2 = std::max(exp2(referenceExponent), AccPrecision<OutType>::normal_min);
         // Get the value of changing the last bit - by shifting the least significant bit to this magnitude
         // i.e. the ULP.
-        double ulpValue = referencePower2 * exp2(-AccPrecision<float>::normal_frac);
+        double ulpValue = referencePower2 * exp2(-AccPrecision<OutType>::normal_frac);
 
         errorBound = ulpValue * ulpNum;
     }
@@ -57,15 +59,35 @@
     const auto elementCount = numElements(refShape);
     TOSA_REF_REQUIRE(elementCount > 0, "[ULP] Invalid shape for reference tensor");
 
-    const double ulp = ulpInfo.ulp;
+    const double ulp    = ulpInfo.ulp;
+    const auto* refData = reinterpret_cast<const double*>(referenceTensor->data);
+    TOSA_REF_REQUIRE(refData != nullptr, "[ULP] Missing data for reference");
+    const auto* refDataEnd = std::next(refData, elementCount);
     switch (implementationTensor->data_type)
     {
         case tosa_datatype_fp32_t: {
-            const auto* refData = reinterpret_cast<const double*>(referenceTensor->data);
-            TOSA_REF_REQUIRE(refData != nullptr, "[ULP] Missing data for reference");
             const auto* impData = reinterpret_cast<const float*>(implementationTensor->data);
             TOSA_REF_REQUIRE(impData != nullptr, "[ULP] Missing data for implementation");
-            const auto* refDataEnd = std::next(refData, elementCount);
+            // Use mismatch to get the location of the first unequal value
+            auto pair = std::mismatch(refData, refDataEnd, impData, std::next(impData, elementCount),
+                                      [ulp](const auto& referenceValue, const auto& implementationValue) {
+                                          return tosaCheckULP(implementationValue, referenceValue, ulp);
+                                      });
+            if (std::get<0>(pair) == refDataEnd)
+            {
+                // No mismatch found
+                return true;
+            }
+            else
+            {
+                auto pos = indexToPosition(std::get<0>(pair) - refData, refShape);
+                WARNING("[Verfier][ULP] Location %s", positionToString(pos).c_str());
+                return false;
+            }
+        }
+        case tosa_datatype_fp16_t: {
+            const auto* impData = reinterpret_cast<const half_float::half*>(implementationTensor->data);
+            TOSA_REF_REQUIRE(impData != nullptr, "[ULP] Missing data for implementation");
             // Use mismatch to get the location of the first unequal value
             auto pair = std::mismatch(refData, refDataEnd, impData, std::next(impData, elementCount),
                                       [ulp](const auto& referenceValue, const auto& implementationValue) {
diff --git a/reference_model/src/verify/verify_utils.cc b/reference_model/src/verify/verify_utils.cc
index 9aa6ba2..3bdc99f 100644
--- a/reference_model/src/verify/verify_utils.cc
+++ b/reference_model/src/verify/verify_utils.cc
@@ -202,7 +202,8 @@
               "TOSA Reference Model has not been built with standard IEEE 754 64-bit float support; Bounds based "
               "verification is invalid");
 
-bool tosaCheckFloatBound(float testValue, double referenceValue, double errorBound)
+template <typename OutType>
+bool tosaCheckFloatBound(OutType testValue, double referenceValue, double errorBound)
 {
     // Both must be NaNs to be correct
     if (std::isnan(referenceValue) || std::isnan(testValue))
@@ -236,8 +237,8 @@
     {
         // We already canonicalized the input such that the reference value is positive
         // so no need to check again here.
-        referenceMin = std::numeric_limits<float>::infinity();
-        referenceMax = std::numeric_limits<float>::infinity();
+        referenceMin = std::numeric_limits<OutType>::infinity();
+        referenceMax = std::numeric_limits<OutType>::infinity();
     }
     else if (referenceValue == 0)
     {
@@ -253,23 +254,23 @@
         referenceMin = referenceValue - errorBound;
 
         // Handle the overflow cases.
-        if (referenceMax > AccPrecision<float>::normal_max)
+        if (referenceMax > AccPrecision<OutType>::normal_max)
         {
-            referenceMax = std::numeric_limits<float>::infinity();
+            referenceMax = std::numeric_limits<OutType>::infinity();
         }
 
-        if (referenceMin > AccPrecision<float>::normal_max)
+        if (referenceMin > AccPrecision<OutType>::normal_max)
         {
-            referenceMin = std::numeric_limits<float>::infinity();
+            referenceMin = std::numeric_limits<OutType>::infinity();
         }
 
         // And the underflow cases.
-        if (referenceMax < AccPrecision<float>::normal_min)
+        if (referenceMax < AccPrecision<OutType>::normal_min)
         {
-            referenceMax = AccPrecision<float>::normal_min;
+            referenceMax = AccPrecision<OutType>::normal_min;
         }
 
-        if (referenceMin < AccPrecision<float>::normal_min)
+        if (referenceMin < AccPrecision<OutType>::normal_min)
         {
             referenceMin = 0.0;
         }
@@ -286,4 +287,8 @@
     }
     return withinBound;
 }
+
+// Instantiate the needed check functions
+template bool tosaCheckFloatBound(float testValue, double referenceValue, double errorBound);
+template bool tosaCheckFloatBound(half_float::half testValue, double referenceValue, double errorBound);
 }    // namespace TosaReference
diff --git a/reference_model/src/verify/verify_utils.h b/reference_model/src/verify/verify_utils.h
index a58950e..45daeac 100644
--- a/reference_model/src/verify/verify_utils.h
+++ b/reference_model/src/verify/verify_utils.h
@@ -17,6 +17,7 @@
 #define VERIFY_UTILS_H_
 
 #include "dtype.h"
+#include "half.hpp"
 #include "types.h"
 
 #include <cstdint>
@@ -135,10 +136,17 @@
     static constexpr double normal_max   = const_exp2(128) - const_exp2(127 - 23);
     static constexpr int32_t normal_frac = 23;
 };
+template <>
+struct AccPrecision<half_float::half>
+{
+    static constexpr double normal_min   = const_exp2(-14);
+    static constexpr double normal_max   = const_exp2(16) - const_exp2(15 - 10);
+    static constexpr int32_t normal_frac = 7;
+};
 
 /// \brief Error bounds check for ULP and ABS_ERROR modes
-bool tosaCheckFloatBound(float testValue, double referenceValue, double errorBound);
-
+template <typename OutType>
+bool tosaCheckFloatBound(OutType testValue, double referenceValue, double errorBound);
 };    // namespace TosaReference
 
 #endif    // VERIFY_UTILS_H_
diff --git a/verif/generator/datagenerator.py b/verif/generator/datagenerator.py
index 0d59084..9de421b 100644
--- a/verif/generator/datagenerator.py
+++ b/verif/generator/datagenerator.py
@@ -68,19 +68,33 @@
 
     def _create_buffer(self, dtype: str, shape: tuple):
         """Helper to create a buffer of the required type."""
-        size = 1
-        for dim in shape:
-            size *= dim
+        size = np.prod(shape)
 
         if dtype == "FP32":
             # Create buffer and initialize to zero
             buffer = (ct.c_float * size)(0)
             size_bytes = size * 4
+        elif dtype == "FP16":
+            size_bytes = size * 2
+            # Create buffer of bytes and initialize to zero
+            buffer = (ct.c_ubyte * size_bytes)(0)
         else:
             raise GenerateError(f"Unsupported data type {dtype}")
 
         return buffer, size_bytes
 
+    def _convert_buffer(self, buffer, dtype: str, shape: tuple):
+        """Helper to convert a buffer to a numpy array."""
+        arr = np.ctypeslib.as_array(buffer)
+
+        if dtype == "FP16":
+            # Convert from bytes back to FP16
+            arr = np.frombuffer(arr, np.float16)
+
+        arr = np.reshape(arr, shape)
+
+        return arr
+
     def _data_gen_array(self, json_config: str, tensor_name: str):
         """Generate the named tensor data and return a numpy array."""
         try:
@@ -106,9 +120,7 @@
         if not result:
             raise GenerateError("Data generate failed")
 
-        arr = np.ctypeslib.as_array(buffer)
-        arr = np.reshape(arr, shape)
-
+        arr = self._convert_buffer(buffer, dtype, shape)
         return arr
 
     def _data_gen_write(
diff --git a/verif/generator/tosa_arg_gen.py b/verif/generator/tosa_arg_gen.py
index 8e88390..193da73 100644
--- a/verif/generator/tosa_arg_gen.py
+++ b/verif/generator/tosa_arg_gen.py
@@ -1415,9 +1415,9 @@
         if (
             error_name is None
             and argsDict["dg_type"] != gtu.ComplianceMode.DOT_PRODUCT
-            and dtype in (DType.FP16, DType.BF16)
+            and dtype in (DType.BF16,)
         ):
-            # TODO - Remove once FP16 and BF16 enabled for DOT_PRODUCT compliance
+            # TODO - Remove once BF16 enabled for DOT_PRODUCT compliance
             # Limit ranges for (non error & non compliance) FP tests by using
             # values that can be multiplied on any axis to not hit infinity/NaN
             IC = shapeList[0][1]
diff --git a/verif/generator/tosa_utils.py b/verif/generator/tosa_utils.py
index 318f296..3d733f4 100644
--- a/verif/generator/tosa_utils.py
+++ b/verif/generator/tosa_utils.py
@@ -55,7 +55,7 @@
     """Types supported by the new data generation and compliance flow."""
     if isinstance(dtype, list) or isinstance(dtype, tuple):
         dtype = dtype[0]
-    return dtype in (DType.FP32,)
+    return dtype in (DType.FP32, DType.FP16)
 
 
 def getOpNameFromOpListName(opName):