MLBEDSW-7028: Fix compiler assert for elementwise op

- Refactored erroneously if statement that allowed illegal
swapping between ifm1 and ifm2 for elementwise operators.

Signed-off-by: Johan Alfven <johan.alfven@arm.com>
Change-Id: Iec571f710824432edac9104d960f199f33a1b241
diff --git a/ethosu/vela/cascade_builder.py b/ethosu/vela/cascade_builder.py
index b042ba7..5573ff7 100644
--- a/ethosu/vela/cascade_builder.py
+++ b/ethosu/vela/cascade_builder.py
@@ -16,7 +16,6 @@
 #
 # Description:
 # Groups Operators in a schedule together to form Cascades.
-from .high_level_command_to_npu_op import ifm_ifm2_correct_order
 from .live_range import ofm_can_reuse_ifm
 from .numeric_util import round_up
 from .operation import NpuBlockType
@@ -98,7 +97,7 @@
             and cost.stripe.height < sched_op.ofm.shape.height
             and sched_op.parent_op.read_offsets[0] is None
             and sched_op.parent_op.read_offsets[1] is None
-            and self.elementwise_cascading_correct_order(sched_op)
+            and self.elementwise_cascadable(sched_op)
             and not sched_op.parent_op.type.is_resize_op()
             and not sched_op.parent_op.type == Op.Conv2DBackpropInputSwitchedBias
             and sched_op.parent_op.attrs.get("padding", None) != Padding.TILE
@@ -127,31 +126,21 @@
         return ifm_size + ifm2_size + ofm_size + self.non_local_mem_usage.get(sched_op, 0)
 
     @staticmethod
-    def elementwise_cascading_conformity(sched_op):
-        """Check the inputs of the op to see if it's a candidate for cascading."""
+    def elementwise_cascadable(sched_op):
+        """Check if the elementwise can be cascaded."""
 
         if sched_op.parent_op.type.is_binary_elementwise_op():
-            # We cannot rule out cascadability if at least one IFM is constant
             ifm = sched_op.parent_op.ifm
             ifm2 = sched_op.parent_op.ifm2
-            ifm_const = ifm.ops != [] and ifm.ops[0].type == Op.Const
-            ifm2_const = ifm2.ops != [] and ifm2.ops[0].type == Op.Const
-            return ifm_const or ifm2_const
-        else:
-            # Either one IFM is not variable or it is not a binary elementwise op - we cannot rule out cascadability
-            return True
+            ofm = sched_op.parent_op.ofm
 
-    @staticmethod
-    def elementwise_cascading_correct_order(sched_op):
-        """Check the inputs of the op to see ifm and ifm2 has correct order."""
+            # IFM must be non-constant/non-scalar/non-broadcast
+            ifm_cascadable = not (ifm.is_const or ifm.is_scalar or ifm.is_broadcast(ofm))
 
-        if sched_op.parent_op.type.is_binary_elementwise_op():
-            ifm2 = sched_op.parent_op.ifm2
-            ifm2_const = ifm2.ops != [] and ifm2.ops[0].type == Op.Const
+            # IFM2 must be constant or scalar
+            ifm2_cascadable = ifm2.is_const or ifm2.is_scalar
 
-            # ifm_ifm2_correct_order needs full shape
-            correct_order = ifm_ifm2_correct_order(sched_op.ifm.shape, sched_op.ifm2.shape)
-            return ifm2_const and correct_order
+            return ifm_cascadable and ifm2_cascadable
         else:
             return True
 
diff --git a/ethosu/vela/scheduler.py b/ethosu/vela/scheduler.py
index ec7380a..021bcc9 100644
--- a/ethosu/vela/scheduler.py
+++ b/ethosu/vela/scheduler.py
@@ -227,19 +227,17 @@
         # Perform an IFM swap for certain binary elementwise operators
         # in order to enable cascading, if the SchedOp conforms to
         # Elementwise cascading rules.
-        if self.op_type.is_binary_elementwise_op() and CascadeBuilder.elementwise_cascading_conformity(self):
-            ifm1 = ps.ifm_tensor
-            ifm2 = ps.ifm2_tensor
-            ofm = ps.ofm_tensor
-            assert ifm1.elements() > 0
-            assert ifm2.elements() > 0
+        # The non-constant/non-scalar/non-broadcast IFM should be the primary input
+        if self.op_type.is_binary_elementwise_op():
+            ifm = self.parent_op.ifm
+            ifm2 = self.parent_op.ifm2
+            ofm = self.parent_op.ofm
 
-            if (
-                # The non-constant IFM should be the primary input
-                (ifm1.ops[0].type == Op.Const and ifm2.ops[0].type != Op.Const)
-                # The non-broadcasted IFM should be the primary input
-                or (ifm1.shape != ofm.shape and ifm2.shape == ofm.shape)
-            ):
+            ifm_can_be_primary = not (ifm.is_const or ifm.is_scalar or ifm.is_broadcast(ofm))
+            ifm2_can_be_primary = not (ifm2.is_const or ifm2.is_scalar or ifm2.is_broadcast(ofm))
+
+            if not ifm_can_be_primary and ifm2_can_be_primary:
+                # IFM2 is the primary input
                 self.reversed_operands = True
                 self.ifm, self.ifm2 = self.ifm2, self.ifm
 
diff --git a/ethosu/vela/tensor.py b/ethosu/vela/tensor.py
index 17d41b1..ba38588 100644
--- a/ethosu/vela/tensor.py
+++ b/ethosu/vela/tensor.py
@@ -436,6 +436,17 @@
     def is_standard_fm(self) -> bool:
         return self.sub_purpose == TensorSubPurpose.Standard and self.purpose == TensorPurpose.FeatureMap
 
+    @property
+    def is_const(self) -> bool:
+        return self.ops != [] and self.ops[0].type == Op.Const
+
+    @property
+    def is_scalar(self) -> bool:
+        return self.shape == [] and self.elements() == 1
+
+    def is_broadcast(self, ofm) -> bool:
+        return self.shape != ofm.shape
+
     def element_size(self) -> int:
         if self.element_size_bytes == 0:
             return self.dtype.size_in_bits() // 8