MLBEDSW-7504: Vela does not keep op version number

We now read operator code version, store it in operator and write it out
to optimized file.
Signed-off-by: wilisa01 <william.isaksson@arm.com>
Change-Id: Idba672531d2e2a0203a85d3ffca9cf65ace85b47
diff --git a/ethosu/vela/operation.py b/ethosu/vela/operation.py
index 6959652..161b17f 100644
--- a/ethosu/vela/operation.py
+++ b/ethosu/vela/operation.py
@@ -471,6 +471,7 @@
         "type",
         "_original_type",
         "name",
+        "version",
         "op_index",
         "attrs",
         "inputs",
@@ -504,6 +505,7 @@
         self.type = op_type
         self._original_type = op_type  # the original type of the operation. once set this shouldn't be changed
         self.name = name
+        self.version = 1  # Used to track original operator version.
         self.attrs: Dict[str, Any] = {}
         self.inputs: List[Optional[Tensor]] = []
         self.outputs: List[Tensor] = []
@@ -549,7 +551,7 @@
 
         # maintain the original type, in cases where the type was changed to something different
         res._original_type = self._original_type
-
+        res.version = self.version
         res.attrs = dict(self.attrs)
         res.inputs = list(self.inputs)
         res.outputs = list(self.outputs)
diff --git a/ethosu/vela/test/test_tflite_reader.py b/ethosu/vela/test/test_tflite_reader.py
index 871545e..d1a8ee6 100644
--- a/ethosu/vela/test/test_tflite_reader.py
+++ b/ethosu/vela/test/test_tflite_reader.py
@@ -1,4 +1,4 @@
-# SPDX-FileCopyrightText: Copyright 2020-2021 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-FileCopyrightText: Copyright 2020-2021, 2023 Arm Limited and/or its affiliates <open-source-office@arm.com>
 #
 # SPDX-License-Identifier: Apache-2.0
 #
@@ -46,25 +46,25 @@
         assert output == expected
 
     parse_op_testdata = [
-        # op_type, opt_serializer, indices, inputs, output, expected
-        (Op.FullyConnected, None, TFLITE_IFM_WEIGHTS_BIAS_INDICES, [0, 1, 2], 3, 3),  # FC
-        (Op.FullyConnected, None, TFLITE_IFM_WEIGHTS_BIAS_INDICES, [0, 1, -1], 3, 3),  # FC disabled Bias
-        (Op.FullyConnected, None, TFLITE_IFM_WEIGHTS_BIAS_INDICES, [0, 1], 3, 3),  # FC no Bias
-        (Op.Conv2DBias, None, TFLITE_IFM_WEIGHTS_BIAS_INDICES, [2, 1, 3], 0, 3),  # Conv2D
-        (Op.Conv2DBackpropInput, None, TFLITE_CONV2D_BACKPROP_INDICES, [0, 1, 2, 3], 4, 4),  # TransposeConv
-        (Op.Conv2DBackpropInput, None, TFLITE_CONV2D_BACKPROP_INDICES, [0, 1, 2], 4, 4),  # TransposeConv no Bias
+        # op_type, opt_serializer, indices, version, inputs, output, expected
+        (Op.FullyConnected, None, TFLITE_IFM_WEIGHTS_BIAS_INDICES, 1, [0, 1, 2], 3, 3),  # FC
+        (Op.FullyConnected, None, TFLITE_IFM_WEIGHTS_BIAS_INDICES, 1, [0, 1, -1], 3, 3),  # FC disabled Bias
+        (Op.FullyConnected, None, TFLITE_IFM_WEIGHTS_BIAS_INDICES, 5, [0, 1], 3, 3),  # FC no Bias
+        (Op.Conv2DBias, None, TFLITE_IFM_WEIGHTS_BIAS_INDICES, 5, [2, 1, 3], 0, 3),  # Conv2D
+        (Op.Conv2DBackpropInput, None, TFLITE_CONV2D_BACKPROP_INDICES, 5, [0, 1, 2, 3], 4, 4),  # TransposeConv
+        (Op.Conv2DBackpropInput, None, TFLITE_CONV2D_BACKPROP_INDICES, 5, [0, 1, 2], 4, 4),  # TransposeConv no Bias
         pytest.param(
-            Op.Conv2DBias, None, TFLITE_IFM_WEIGHTS_BIAS_INDICES, [0, -1, 1], 3, 3, marks=pytest.mark.xfail
+            Op.Conv2DBias, None, TFLITE_IFM_WEIGHTS_BIAS_INDICES, 5, [0, -1, 1], 3, 3, marks=pytest.mark.xfail
         ),  # Conv2D no Weights
     ]
 
-    @pytest.mark.parametrize("op_type, opt_serializer, indices, inputs, output, expected", parse_op_testdata)
-    def test_parse_operator(self, op_type, opt_serializer, indices, inputs, output, expected):
+    @pytest.mark.parametrize("op_type, opt_serializer, indices, version, inputs, output, expected", parse_op_testdata)
+    def test_parse_operator(self, op_type, opt_serializer, indices, version, inputs, output, expected):
         with patch.object(TFLiteSubgraph, "__init__", lambda self, graph, subraph: None):
             # Mock a TFLiteSubGraph
             sg = TFLiteSubgraph(None, None)
             sg.graph = MagicMock()
-            sg.graph.operator_codes = [(op_type, opt_serializer, "", indices)]
+            sg.graph.operator_codes = [(op_type, opt_serializer, "", indices, version)]
 
             # Mock a couple of tensors
             sg.tensors = [MagicMock() for _ in range(5)]
diff --git a/ethosu/vela/tflite_reader.py b/ethosu/vela/tflite_reader.py
index 061f362..85acb6b 100644
--- a/ethosu/vela/tflite_reader.py
+++ b/ethosu/vela/tflite_reader.py
@@ -117,7 +117,7 @@
         return tens
 
     def parse_operator(self, op_index, op_data):
-        op_type, opt_serializer, custom_code, indices = self.graph.operator_codes[op_data.OpcodeIndex()]
+        op_type, opt_serializer, custom_code, indices, version = self.graph.operator_codes[op_data.OpcodeIndex()]
         inputs = [self.tensors[idx] if idx != -1 else None for idx in op_data.InputsAsNumpy()]
         outputs = [self.tensors[idx] if idx != -1 else None for idx in op_data.OutputsAsNumpy()]
         intermediates = []
@@ -130,6 +130,7 @@
         inputs = align_tensor_indices_to_nng(op_type, indices, inputs)
         op = Operation(op_type, name)
         op.op_index = op_index
+        op.version = version
         op.inputs = inputs
         op.outputs = outputs
         op.intermediates = intermediates
@@ -338,7 +339,7 @@
         custom_code = None
         if c == BuiltinOperator.CUSTOM:
             custom_code = decode_str(code.CustomCode())
-        return op_type, ser, custom_code, indices
+        return op_type, ser, custom_code, indices, code.Version()
 
 
 def read_tflite(filename, batch_size, feed_dict, output_node_names, initialisation_nodes):
diff --git a/ethosu/vela/tflite_writer.py b/ethosu/vela/tflite_writer.py
index 2e7345c..8c03f05 100644
--- a/ethosu/vela/tflite_writer.py
+++ b/ethosu/vela/tflite_writer.py
@@ -108,8 +108,8 @@
                             if inp is not None and inp.src_tensor is not None:
                                 op.inputs[idx] = inp.src_tensor
 
-        # list of tuple(Op, string); the custom code is only used for 3rd party custom operators
-        self.operator_codes = sorted(set((op.type, op.attrs.get("custom_code", "")) for op in all_ops))
+        # list of tuple(Op, string, op.version); the custom code is only used for 3rd party custom operators
+        self.operator_codes = sorted(set((op.type, op.attrs.get("custom_code", ""), op.version) for op in all_ops))
         self.operator_code_map = {}
 
     def align_nng_inputs_to_tflite(self, op):
@@ -176,7 +176,7 @@
 
         return buffer_map
 
-    def serialise_operator_code(self, idx, op_type, custom_code):
+    def serialise_operator_code(self, idx, op_type, custom_code, version):
         builder = self.builder
         custom_code_offset = None
         if op_type == Op.Custom:
@@ -207,6 +207,7 @@
         OperatorCode.OperatorCodeStart(builder)
         OperatorCode.OperatorCodeAddDeprecatedBuiltinCode(builder, tf_code if tf_code < 127 else 127)
         OperatorCode.OperatorCodeAddBuiltinCode(builder, tf_code)
+        OperatorCode.OperatorCodeAddVersion(builder, version)
         if custom_code_offset is not None:
             OperatorCode.OperatorCodeAddCustomCode(builder, custom_code_offset)
 
@@ -455,7 +456,10 @@
     def serialise_model(self):
         builder = self.builder
         operator_code_offset = self.write_offset_vector(
-            [self.serialise_operator_code(idx, optype, code) for idx, (optype, code) in enumerate(self.operator_codes)]
+            [
+                self.serialise_operator_code(idx, optype, code, version)
+                for idx, (optype, code, version) in enumerate(self.operator_codes)
+            ]
         )
 
         description = builder.CreateString("Vela Optimised")