MLBEDSW-6769: Fix odd stripe heights for upscaling

Output diffs were found to be caused by odd input stripe heights,
despite the input being an upscaling operator.

Signed-off-by: erik.andersson@arm.com <erik.andersson@arm.com>
Change-Id: Ia3791d815250364cfe7a38c3ed0e30768d64ca08
diff --git a/ethosu/vela/scheduler.py b/ethosu/vela/scheduler.py
index e9f38b4..9dca63a 100644
--- a/ethosu/vela/scheduler.py
+++ b/ethosu/vela/scheduler.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2021 Arm Limited or its affiliates. All rights reserved.
+# Copyright (C) 2022 Arm Limited or its affiliates. All rights reserved.
 #
 # SPDX-License-Identifier: Apache-2.0
 #
@@ -45,6 +45,7 @@
 from .architecture_allocator import find_block_config
 from .architecture_allocator import get_ifm_area_required
 from .architecture_allocator import to_upscale
+from .architecture_allocator import is_nearest
 from .architecture_features import ArchitectureFeatures
 from .architecture_features import Block
 from .cascade_builder import CascadeBuilder
@@ -906,8 +907,25 @@
             cost.cycles = self.estimate_op_performance(sched_op, cost.block_config, sched_op.ofm.shape.depth)
             striped_schedule.cost_map[sched_op] = cost
 
-            # Calculate the preceeding Op's stripe
-            height = stripe.height + stripe.height % to_upscale(sched_op.resampling_mode)
+            # Calculate the preceeding Op's stripe.
+
+            # In certain cases where an upscaling Op is cascaded,
+            # it may get instructed to produce an odd stripe height.
+            # Thus we need to force it back to even heights.
+            force_even_stripe_heights = False
+            for op in self.sched_ops:
+                # Check if the cascade has a Nearest Neighbor-op.
+                # If that is the case, force the stripes to be even.
+                if (
+                    ref_cost.get(op, None)
+                    and ref_cost.get(sched_op, None)
+                    and ref_cost[op].cascade == ref_cost[sched_op].cascade
+                    and is_nearest(op.resampling_mode)
+                ):
+                    force_even_stripe_heights = True
+                    break
+            upscaling_remainder = stripe.height % to_upscale(sched_op.resampling_mode)
+            height = stripe.height + (stripe.height % 2 if force_even_stripe_heights else upscaling_remainder)
             stripe = sched_op.ifm.shape.with_height(height * sched_op.kernel.stride.y)
 
         return striped_schedule
@@ -1012,7 +1030,6 @@
         possible_stripes = [
             final_ofm_shape.with_height(stripe_h) for stripe_h in range(1, final_ofm_shape.height // 2 + 1)
         ]
-
         # Propose different striping - the possible stripes are proposed similarly to a binary search
         best_schedule = None
         iteration = 0