MLBEDSW-4838 Added basic TOSA support.

Added basic TOSA support, enabling Vela to
read and compile  a .tosa file corresponding to
CONV2D + Rescale + Clamp, and writing it to an
optimized .tflite file.

The optimized .tflite file, will in this case, hold
a commandstream where the Rescale and Clamp has been
fused into the CONV2D.

The optimized tflite file is not output from Vela.

  -Added support to read .tosa file into Vela
    internal structure.
      - Added tosa_reader.py, tosa_mapper.py and
        helper files stored under tosa/
      - Support for this limited to ~10 ops

    -Added reader_util.py for functions common
    for TOSA and TFLite

    -Added tosa_graph_optimiser.py
      -Added support to fuse Rescale into convolution
      -Modified handling for padding

    -Added support to fuse Clamp to previous op

    -Added graph_optimiser_util.py
      -Moved functions common for TOSA/TFLite graph
       optimization to this file.

    -Renamed graph_optimiser.py to tflite_graph_optmiser.py

    -Added separate tosa_supported_operators.py

    -Added supported_operator_util.py
       -For functions in common for TOSA/TFLite

Signed-off-by: Patrik Gustavsson <patrik.gustavsson@arm.com>
Change-Id: Ic3c540504ec8c5eb4771397fdc6882050ecf33ab
diff --git a/ethosu/vela/operation.py b/ethosu/vela/operation.py
index 6bd955d..0558e52 100644
--- a/ethosu/vela/operation.py
+++ b/ethosu/vela/operation.py
@@ -238,6 +238,8 @@
     Relu = OperatorInfo(indices=IFM_INDICES)
     Relu6 = OperatorInfo(indices=IFM_INDICES)
     ReluN1To1 = OperatorInfo(indices=IFM_INDICES)
+    ReluN = OperatorInfo(indices=IFM_INDICES)  # TOSA specific
+    Rescale = OperatorInfo(indices=IFM_INDICES)  # TOSA specific
     RescaleAdd = OperatorInfo(block_type=NpuBlockType.ElementWise, indices=IFM_IFM2_INDICES)
     Reshape = OperatorInfo(indices=IFM_INDICES)
     ResizeBilinear = OperatorInfo(block_type=NpuBlockType.Pooling, indices=IFM_INDICES)
@@ -321,7 +323,7 @@
         return self.info.block_type == NpuBlockType.ElementWise and not self.info.is_unary
 
     def is_relu_op(self):
-        return self in (Op.Relu, Op.Relu6, Op.ReluN1To1, Op.Clip)
+        return self in (Op.Relu, Op.Relu6, Op.ReluN1To1, Op.ReluN, Op.Clip)
 
     def is_activation_op(self):
         return self.is_relu_op() or self in (Op.Tanh, Op.Sigmoid, Op.Softmax, Op.LUT, Op.HardSwish)
@@ -374,7 +376,20 @@
         return res
 
 
-def create_activation_function(op_type: Op) -> ActivationFunction:
+class ExplicitScaling:
+    """Explicit scaling parameters"""
+
+    def __init__(self, per_channel, shift, multiplier):
+        self.per_channel = per_channel
+        self.shift = shift
+        self.multiplier = multiplier
+
+    def clone(self):
+        res = copy.copy(self)
+        return res
+
+
+def create_activation_function(op_type: Op, min=None, max=None) -> ActivationFunction:
     """Creates activation function with min/max depending on op_type"""
     act = ActivationFunction(op_type)
     if op_type == Op.Relu:
@@ -393,6 +408,15 @@
         act.max = 1.0
     elif op_type == Op.HardSwish:
         act.min = 0.0
+    if op_type == Op.Clip:
+        assert min is not None and max is not None
+        act.min = min
+        act.max = max
+    elif op_type == Op.ReluN:
+        assert max is not None
+        act.min = 0.0
+        act.max = max
+
     return act
 
 
@@ -436,6 +460,7 @@
         "read_offsets",
         "read_shapes",
         "rounding_mode",
+        "explicit_scaling",
         "low_precision_scaling",
         "write_offset",
         "write_shape",
@@ -470,6 +495,8 @@
         self.read_offsets: List[Shape4D] = [None, None]  # offset for [ifm, ifm2]
         self.read_shapes: List[Shape4D] = [None, None]  # read shape for [ifm, ifm2]
         self.rounding_mode: Optional[NpuRoundingMode] = None
+        # Rescale op in TOSA supplies explicit multiplier and shift values
+        self.explicit_scaling: Optional[ExplicitScaling] = None
         # The Mean operator (implemented as a depthwise convolution) requires scaling
         # to be calculated differently in one case. In that case, this is set to True.
         self.low_precision_scaling = False
@@ -498,6 +525,7 @@
         res.read_offsets = list(self.read_offsets)
         res.read_shapes = list(self.read_shapes)
         res.rounding_mode = self.rounding_mode
+        res.explicit_scaling = self.explicit_scaling
         res.low_precision_scaling = self.low_precision_scaling
 
         return res