MLBEDSW-6298: MLCE: Unable to find a valid block config

 - Fixed a bug due to ResizeBilinear modifying the attributes of a
shared IFM
 - The ifm_resampling_mode is now an attribute of an operator rather
than a tensor
 - Changed all calls to try_block_config() to use the attribute rather
than recalculating it in multiple places

Signed-off-by: Tim Hall <tim.hall@arm.com>
Change-Id: I4641e9cd6b049bd4186776d98e3e751c5e5bcc06
diff --git a/ethosu/vela/high_level_command_to_npu_op.py b/ethosu/vela/high_level_command_to_npu_op.py
index f7c91aa..c822132 100644
--- a/ethosu/vela/high_level_command_to_npu_op.py
+++ b/ethosu/vela/high_level_command_to_npu_op.py
@@ -49,6 +49,7 @@
 from .data_type import DataType
 from .debug_database import DebugDatabase
 from .errors import UnsupportedFeatureError
+from .ethos_u55_regs.ethos_u55_regs import resampling_mode
 from .high_level_command_stream import Box
 from .high_level_command_stream import Command
 from .high_level_command_stream import DMA
@@ -104,6 +105,14 @@
 }
 
 
+# inverse of the resampling_mode_map in the register command stream generator
+resampling_mode_inv_map = {
+    resampling_mode.NONE: NpuResamplingMode.NONE,
+    resampling_mode.NEAREST: NpuResamplingMode.NEAREST,
+    resampling_mode.TRANSPOSE: NpuResamplingMode.TRANSPOSE,
+}
+
+
 def ifm_ifm2_correct_order(ifm_shape: List[int], ifm2_shape: List[int]) -> bool:
     if ifm_shape == []:
         # Scalar needs to be in IFM2
@@ -193,17 +202,6 @@
     return mem_limits
 
 
-def get_upscale(op: Operation) -> NpuResamplingMode:
-    upscale = NpuResamplingMode.NONE
-    if op.type == Op.ResizeBilinear:
-        # perform nearest neighbor upscale
-        upscale = NpuResamplingMode.NEAREST
-    elif op.type == Op.Conv2DBackpropInputSwitchedBias:
-        # perform insert zero upscale
-        upscale = NpuResamplingMode.TRANSPOSE
-    return upscale
-
-
 def get_double_buffer_offset(arch: ArchitectureFeatures, range_index: int, core: int) -> int:
     """Returns 0 if the first half of a double buffer should be used, 1 if the second half should be used"""
     return ((range_index - core) // arch.ncores) % 2
@@ -409,7 +407,7 @@
     if not op.type.is_elementwise_op():
         npu_op.padding = create_padding(cmd, op)
         npu_op.kernel = to_npu_kernel(op.kernel)
-    npu_op.ifm_upscale = get_upscale(op)
+    npu_op.ifm_upscale = resampling_mode_inv_map[op.ifm_resampling_mode]
     return npu_op
 
 
diff --git a/ethosu/vela/operation.py b/ethosu/vela/operation.py
index 5a6423d..d5b92d8 100644
--- a/ethosu/vela/operation.py
+++ b/ethosu/vela/operation.py
@@ -31,6 +31,7 @@
 
 from .api import NpuRoundingMode
 from .errors import VelaError
+from .ethos_u55_regs.ethos_u55_regs import resampling_mode
 from .numeric_util import full_shape
 from .shape4d import Shape4D
 
@@ -488,6 +489,7 @@
         "low_precision_scaling",
         "write_offset",
         "write_shape",
+        "ifm_resampling_mode",
     )
 
     def __init__(self, op_type: Op, name: str):
@@ -530,6 +532,7 @@
         # E.g. an operation that only fills the bottom row of an OFM of size 1x10x8x1 would have
         # write_offset 0,9,0,0, write_shape 1,1,8,1
         self.write_shape: Optional[Shape4D] = None
+        self.ifm_resampling_mode: resampling_mode = resampling_mode.NONE
 
     def clone(self, suffix="_clone"):
         res = Operation(self.type, self.name + suffix)
diff --git a/ethosu/vela/scheduler.py b/ethosu/vela/scheduler.py
index 73133bc..e8e4909 100644
--- a/ethosu/vela/scheduler.py
+++ b/ethosu/vela/scheduler.py
@@ -170,7 +170,7 @@
         self.op_type = ps.primary_op.type
         self.activation = ps.primary_op.activation
         self.kernel = ps.primary_op.kernel
-        self.resampling_mode = ps.primary_op.ifm.resampling_mode
+        self.resampling_mode = ps.primary_op.ifm_resampling_mode
         self.uses_scalar = ps.primary_op.ifm2 is not None and (
             ps.primary_op.ifm.shape == [] or ps.primary_op.ifm2.shape == []
         )
diff --git a/ethosu/vela/tensor.py b/ethosu/vela/tensor.py
index 19016a0..783f459 100644
--- a/ethosu/vela/tensor.py
+++ b/ethosu/vela/tensor.py
@@ -36,7 +36,6 @@
 from .data_type import DataType
 from .errors import UnsupportedFeatureError
 from .errors import VelaError
-from .ethos_u55_regs.ethos_u55_regs import resampling_mode
 from .numeric_util import full_shape
 from .operation import Op
 from .operation import Operation
@@ -367,7 +366,6 @@
         "element_size_bytes",
         "block_traversal",
         "equivalence_id",
-        "resampling_mode",
         "src_tensor",
         "needs_linear_format",
         "ifm_write_protected",
@@ -414,7 +412,6 @@
         # quantization parameters
         self.quantization: Optional[QuantizationParameters] = None
         self.block_traversal: TensorBlockTraversal = TensorBlockTraversal.Default
-        self.resampling_mode: resampling_mode = resampling_mode.NONE
 
         self.needs_linear_format = True
         self.ifm_write_protected = False
diff --git a/ethosu/vela/tflite_graph_optimiser.py b/ethosu/vela/tflite_graph_optimiser.py
index 576ead0..fb8a08c 100644
--- a/ethosu/vela/tflite_graph_optimiser.py
+++ b/ethosu/vela/tflite_graph_optimiser.py
@@ -268,7 +268,7 @@
         # flip the inputs
         op.inputs[0], op.inputs[2] = op.inputs[2], op.inputs[0]
         op.type = Op.Conv2DBackpropInputSwitchedBias
-        op.ifm.resampling_mode = resampling_mode.TRANSPOSE
+        op.ifm_resampling_mode = resampling_mode.TRANSPOSE
 
         # Update strides
         op.attrs.update({"stride_w": 1, "stride_h": 1, "strides": (1, 1, 1, 1)})
@@ -312,7 +312,7 @@
     else:
         shape_modifier = 0
         op.attrs["padding"] = Padding.SAME
-    op.inputs[0].resampling_mode = resampling_mode.NEAREST
+    op.ifm_resampling_mode = resampling_mode.NEAREST
 
     upscaled_shape = np.array(op.ifm_shapes[0].get_hw_as_list())
     out_shape = np.array(op.ofm_shapes[0].get_hw_as_list())
@@ -1128,7 +1128,6 @@
 
 def add_attrs_to_resizebilinear(op, arch, nng):
     if op.type == Op.ResizeBilinear and op.run_on_npu:
-        input_tensor = op.inputs[0]
         input_shape = op.ifm_shapes[0]
         upscaled_height = input_shape.height * 2
         upscaled_width = input_shape.width * 2
@@ -1147,7 +1146,7 @@
             op.attrs["padding"] = Padding.VALID
         else:
             return op
-        input_tensor.resampling_mode = resampling_mode.NEAREST
+        op.ifm_resampling_mode = resampling_mode.NEAREST
         op.attrs.update({"strides": (1, 1, 1, 1), "ksize": (1, 2, 2, 1)})
     return op