MLBEDSW-6915: MLCE - Missing operators in Debug DB

- Adds missing operators and type conversion recording to DebugDB

Change-Id: If76b0b430bbe73ae1469024c3160ecf0eea26abe
Signed-off-by: wilisa01 <william.isaksson@arm.com>
diff --git a/ethosu/vela/compiler_driver.py b/ethosu/vela/compiler_driver.py
index 014d12a..439b954 100644
--- a/ethosu/vela/compiler_driver.py
+++ b/ethosu/vela/compiler_driver.py
@@ -134,7 +134,7 @@
 
 
 def _record_operator(op, arch):
-    if op.type != Op.Const:
+    if op.type not in (Op.Const, Op.Placeholder):
         DebugDatabase.add_source(op)
 
 
diff --git a/ethosu/vela/debug_database.py b/ethosu/vela/debug_database.py
index bd45b46..c1389e5 100644
--- a/ethosu/vela/debug_database.py
+++ b/ethosu/vela/debug_database.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-2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
 #
 # SPDX-License-Identifier: Apache-2.0
 #
@@ -61,39 +61,58 @@
             [uid, str(op.type), op.kernel.width, op.kernel.height, ofm_shape[-2], ofm_shape[-3], ofm_shape[-1]]
         )
 
+    # Ops are added when their type changes, and after optimisation. If an op was already 
+    # added before optimisation was finished it will only be added again if it's entry
+    # has changed in any way from it's previous entry.
     @classmethod
     def add_optimised(cls, parent: Operation, op: Operation):
         assert isinstance(parent, Operation) and isinstance(op, Operation)
+        if parent not in cls._sourceUID:
+            # If the parent wasn't in the source network try to look it
+            # up in the optimised network and use that op's source parent.
+            if parent in cls._optimisedUID:
+                src_uid = cls._optimisedUID[parent][1]
+            else:
+                if DebugDatabase.show_warnings:
+                    print("Debug Database: Associated parent '{0}' not in network".format(parent.type))
+                src_uid = DebugDatabase.NULLREF
+        else:
+            src_uid = cls._sourceUID[parent]
+
+        # correction for missing shapes
+        if len(op.ofm_shapes) == 0:
+            ofm_shape = Shape4D(op.outputs[0].shape)
+        else:
+            ofm_shape = op.ofm_shapes[0]
+
+        next_uid = len(cls._optimisedTable)  # required because no longer 1:1 UID->table correspondence
+        opt_uid = cls._optimisedUID.get(op, (next_uid, 0))[0]  # already seen or next uid (if not seen)
+
+        opt_table_entry = [
+            opt_uid,
+            src_uid,
+            str(op.type),
+            op.kernel.width,
+            op.kernel.height,
+            ofm_shape.width,
+            ofm_shape.height,
+            ofm_shape.depth,
+        ]
+
         if op not in cls._optimisedUID:
-            if parent not in cls._sourceUID:
-                # The the parent wasn't in the source network try to look it
-                # up in the optimised network and use that op's source parent.
-                if parent in cls._optimisedUID:
-                    src_uid = cls._optimisedUID[parent][1]
-                else:
-                    if DebugDatabase.show_warnings:
-                        print("Debug Database: Associated parent '{0}' not in network".format(parent.type))
-                    src_uid = DebugDatabase.NULLREF
-            else:
-                src_uid = cls._sourceUID[parent]
-            uid = len(cls._optimisedUID)
-            cls._optimisedUID[op] = (uid, src_uid)
-            if len(op.ofm_shapes) == 0:
-                ofm_shape = Shape4D(op.outputs[0].shape)
-            else:
-                ofm_shape = op.ofm_shapes[0]
-            cls._optimisedTable.append(
-                [
-                    uid,
-                    src_uid,
-                    str(op.type),
-                    op.kernel.width,
-                    op.kernel.height,
-                    ofm_shape.width,
-                    ofm_shape.height,
-                    ofm_shape.depth,
-                ]
-            )
+            # optimised op does not exist
+            cls._optimisedUID[op] = (next_uid, src_uid)
+            cls._optimisedTable.append(opt_table_entry)
+        else:
+            # optimised op already exists
+            existing_entry = cls._optimisedTable[
+                cls._optimisedUID[op][0]
+            ]  # Existing entry is where the 'op' object was last inserted
+            if opt_table_entry != existing_entry:
+                # only add again if it's changed in any way
+                opt_table_entry[0] = next_uid  # give it a new unique id (required)
+                cls._optimisedUID[op] = (next_uid, src_uid)
+                cls._optimisedTable.append(opt_table_entry)
 
     @classmethod
     def add_stream(cls, key):
diff --git a/ethosu/vela/graph_optimiser_util.py b/ethosu/vela/graph_optimiser_util.py
index fde01cf..4e233c4 100644
--- a/ethosu/vela/graph_optimiser_util.py
+++ b/ethosu/vela/graph_optimiser_util.py
@@ -257,7 +257,6 @@
         cons_op.read_shapes[1] = op.read_shapes[0]
         cons_op.set_input_tensor(op.ifm, cons_op.type.info.indices.ifms[1])
         cons_op.ifm_shapes[1] = op.ifm_shapes[0]
-
     op.ofm.consumer_list.remove(cons_op)
     op.ofm.ops = []
     op.ifm.consumer_list.remove(op)
@@ -270,7 +269,7 @@
 
 
 def record_optimised(op, arch):
-    if op.type != Op.Const:
+    if op.type not in (Op.Const, Op.Placeholder):
         DebugDatabase.add_optimised(op, op)
 
 
@@ -392,12 +391,12 @@
 
             weight_tensor.values = np.transpose(weight_tensor.values, (0, 1, 3, 2))
             weight_tensor.set_all_shapes(list(weight_tensor.values.shape))
+            DebugDatabase.add_optimised(op, op)
         else:
             raise UnsupportedFeatureError(
                 f"Unsupported 'DEPTHWISE_CONV_2D' with depth_multiplier = {op.attrs['depth_multiplier']},",
                 f" ifm channels = {ifm_shape.depth}, ofm channels = {ofm_shape.depth}",
             )
-        DebugDatabase.add_optimised(op, op)
     return op
 
 
@@ -426,4 +425,5 @@
     lut_tensor = lut.create_lut_tensor(op.name + "_values", lut_values, DataType.int8)
     op.set_activation_lut(lut_tensor)
     op.set_ifm_ofm_shapes()
+    DebugDatabase.add_optimised(op, op)
     return op
diff --git a/ethosu/vela/tflite_graph_optimiser.py b/ethosu/vela/tflite_graph_optimiser.py
index b858f64..36c1de5 100644
--- a/ethosu/vela/tflite_graph_optimiser.py
+++ b/ethosu/vela/tflite_graph_optimiser.py
@@ -76,10 +76,10 @@
     avgpool_op.write_offset = write_offset
     avgpool_op.write_shape = ifm_shape
     ofm.ops.append(avgpool_op)
-    DebugDatabase.add_optimised(concat_op, avgpool_op)
     avgpool_op.ifm_shapes.append(ifm_shape)
     avgpool_op.ofm_shapes.append(concat_op.ofm_shapes[0])
     avgpool_op.memory_function = Op.ConcatSliceWrite
+    DebugDatabase.add_optimised(concat_op, avgpool_op)
     return avgpool_op
 
 
@@ -279,6 +279,7 @@
 
         # Update strides
         op.attrs.update({"stride_w": 1, "stride_h": 1, "strides": (1, 1, 1, 1)})
+        DebugDatabase.add_optimised(op, op)
 
     return op
 
@@ -301,6 +302,7 @@
     op.inputs[1] = op.inputs[0]
     op.inputs[0] = tens
     op.set_ifm_ofm_shapes()
+    DebugDatabase.add_optimised(op, op)
 
     return op
 
@@ -390,6 +392,7 @@
 
     # finally update the shape incase we've change the tensor shapes or connections
     op.set_ifm_ofm_shapes()
+    DebugDatabase.add_optimised(op, op)
 
     return op
 
@@ -433,6 +436,7 @@
         pre_op = scaled_op
 
         scaled_op.set_ifm_ofm_shapes()
+        DebugDatabase.add_optimised(op, scaled_op)
 
     # Last x2 upscaling
     if n > 1:
@@ -463,6 +467,7 @@
     scaled_op.outputs = outputs
     scaled_op.outputs[0].ops = [scaled_op]
     scaled_op.set_ifm_ofm_shapes()
+    DebugDatabase.add_optimised(op, scaled_op)
 
     return op
 
@@ -531,6 +536,7 @@
         avgpool_op.add_input_tensor(ifm)
         avgpool_op.set_output_tensor(intermediate_tens)
         avgpool_op.set_ifm_ofm_shapes()
+        DebugDatabase.add_optimised(op, op)
 
         dw_conv = Operation(Op.DepthwiseConv2DBias, "depthwise_conv")
         dw_conv._original_type = Op.ResizeBilinear
@@ -592,6 +598,8 @@
                 fixup_bias_tensors(dw_conv, None, None, dtype=DataType.int32)
 
                 dw_conv.set_ifm_ofm_shapes()
+                DebugDatabase.add_optimised(op, dw_conv)
+
                 dw_conv = dw_conv.clone(f"_{index}")
         return op
 
@@ -674,6 +682,7 @@
         act_op.add_input_tensor(intermediate_tens)
         op.set_output_tensor(intermediate_tens)
         act_op.set_ifm_ofm_shapes()
+        DebugDatabase.add_optimised(op, act_op)
 
 
 def rewrite_stridedslice_output(op, arch, nng):
@@ -955,6 +964,7 @@
                     fm_id = ofm.clone(op.name + "_id", set_unique=True)
                     mul_identity.set_output_tensor(fm_id)
                     mul_identity.set_ifm_ofm_shapes()
+                    DebugDatabase.add_optimised(op, mul_identity)
 
                 # Combine scaled and alpha multiplied values
                 max_op = Operation(Op.Maximum, op.name + "_max")
@@ -1389,7 +1399,7 @@
         prev_op.set_activation_lut(op.activation_lut)
     # Bypass op
     prev_op.set_output_tensor(ofm)
-    DebugDatabase.add_optimised(op, prev_op)
+    DebugDatabase.add_optimised(prev_op, prev_op)
     return op
 
 
@@ -1482,6 +1492,8 @@
         op.attrs["padding"] = Padding.EXPLICIT
         op.attrs["explicit_padding"] = (top, left, bottom, right)
         op.set_ifm_ofm_shapes()
+        DebugDatabase.add_optimised(op, op)
+
     return op
 
 
@@ -1682,6 +1694,7 @@
 
         # If the AvgPool version is used, we don't need to do anything else
         if op.type == Op.AvgPool:
+            DebugDatabase.add_optimised(op, op)
             return op
 
         # Make unit weight tensor quantization
@@ -1712,6 +1725,7 @@
         # Add bias tensor
         bias_shape = [shape[-1]]
         op.inputs.append(create_const_tensor("bias", bias_shape, DataType.int32, np.ones(bias_shape) * bias))
+        DebugDatabase.add_optimised(op, op)
 
     return op
 
@@ -1803,7 +1817,6 @@
 
         # Convert this SHAPE op to const
         op.type = Op.Const
-        DebugDatabase.add_optimised(op, op)
 
         # Add size calculation to shape output tensors
         ofm.values = np.array(ifm.shape)