Update tensor ops ERROR_IF criteria

Update to ref model to check ERROR_IF criteria for pooling
and convolution ops to match specification

Update to tosa_verif_build_tests to produce valid test ranges and
new ERROR_IF tests
Plus update pooling ops big kernel to 9 (from 6) for better testing
coverage and set dilation to 1 and add out_pad bottom & right for
transpose_conv2d to match specification

Signed-off-by: Jeremy Johnson <jeremy.johnson@arm.com>
Change-Id: Ic5759872d40ae8d3f3d07043d9a0f2fa0244d72e
diff --git a/verif/generator/tosa_arg_gen.py b/verif/generator/tosa_arg_gen.py
index e3492cd..f63a7df 100644
--- a/verif/generator/tosa_arg_gen.py
+++ b/verif/generator/tosa_arg_gen.py
@@ -1031,7 +1031,9 @@
             # Can't use stride=0, as it is used to derive output shape, as a divisor
             s_vals = [testGen.rng.choice(range(-5, 0))]
         else:
-            s_vals = [x for x in range(1, testGen.args.max_conv_stride + 1)]
+            # Stride must be greater than 1 to force non-integer error
+            startStride = 1 if error_name != ErrorIf.PoolingOutputShapeNonInteger else 2
+            s_vals = [x for x in range(startStride, testGen.args.max_conv_stride + 1)]
         strides = {x for x in itertools.product(*([s_vals] * k_rank))}
         if error_name == ErrorIf.DilationSmallerOne:
             d_vals = [testGen.rng.choice(range(-5, 1))]
@@ -1055,7 +1057,7 @@
 
         # There are too many parameter combinations, so generate them sparsely,
         # very sparse for negative tests
-        sparsity_factor = 2 if error_name else 100
+        sparsity_factor = 2 if error_name else 120
         sparsity = len(paddings) * len(strides) * len(dilations) // sparsity_factor + 1
         # If there are only a small number of tests, just select them all
         if sparsity < 13:
@@ -1084,16 +1086,37 @@
                         and (ifm_shape[2] + p[2] + p[3]) > d[1]
                         and (k_rank < 3 or ((ifm_shape[3] + p[4] + p[5]) > d[2]))
                     ):
-                        arg_list.append(
-                            (
-                                "st{}_pad{}_dilat{}".format(
-                                    "".join([str(x) for x in s]),
-                                    "".join([str(x) for x in p]),
-                                    "".join([str(x) for x in d]),
-                                ),
-                                [s, p, d],
+                        remainders = []
+                        for index in range(k_rank):
+                            pad_offset = index * 2
+                            remainders.append(
+                                (
+                                    ifm_shape[index + 1]
+                                    - 1
+                                    + p[pad_offset]
+                                    + p[pad_offset + 1]
+                                    - (k[index] - 1) * d[index]
+                                )
+                                % s[index]
                             )
-                        )
+                        if (
+                            # the parameters must produce integer exact output
+                            error_name != ErrorIf.ConvOutputShapeNonInteger
+                            and max(remainders) == 0
+                        ) or (
+                            error_name == ErrorIf.ConvOutputShapeNonInteger
+                            and max(remainders) > 0
+                        ):
+                            arg_list.append(
+                                (
+                                    "st{}_pad{}_dilat{}".format(
+                                        "".join([str(x) for x in s]),
+                                        "".join([str(x) for x in p]),
+                                        "".join([str(x) for x in d]),
+                                    ),
+                                    [s, p, d],
+                                )
+                            )
                     n += 1
 
         return arg_list
@@ -1116,17 +1139,16 @@
             p_vals = [testGen.rng.choice(range(-5, 0))]
         else:
             p_vals = [x for x in range(0, testGen.args.max_conv_padding + 1)]
-        paddings = {x for x in itertools.product(*([p_vals] * 2))}
+        paddings = {x for x in itertools.product(*([p_vals] * 4))}
         if error_name == ErrorIf.StrideSmallerOne:
             # Can't use stride=0, as it is used to derive output shape, as a divisor
             s_vals = [testGen.rng.choice(range(-5, 0))]
         else:
             s_vals = [x for x in range(1, testGen.args.max_conv_stride + 1)]
         strides = {x for x in itertools.product(*([s_vals] * 2))}
-        if error_name == ErrorIf.DilationSmallerOne:
-            d_vals = [testGen.rng.choice(range(-5, 1))]
-        else:
-            d_vals = [x for x in range(1, testGen.args.max_conv_dilation + 1)]
+        # Dilation is not supported by the specification for transpose conv2d
+        # TODO: Remove this completely when schema has been updated
+        d_vals = [1]
         dilations = {x for x in itertools.product(*([d_vals] * 2))}
 
         if not error_name:
@@ -1134,16 +1156,14 @@
             if max(ifm_shape) < 64:
                 bigPadding = 9
                 paddings.update(
-                    {x for x in itertools.product(*([[0, bigPadding]] * 2))}
+                    {x for x in itertools.product(*([[0, bigPadding]] * 4))}
                 )
             bigStride = 8
             strides.update({x for x in itertools.product(*([[1, bigStride]] * 2))})
-            bigDilation = 7
-            dilations.update({x for x in itertools.product(*([[1, bigDilation]] * 2))})
 
         # There are too many parameter combinations, so generate them sparsely,
         # very sparse for negative tests
-        sparsity_factor = 2 if error_name else 100
+        sparsity_factor = 2 if error_name else 10
         sparsity = len(paddings) * len(strides) * len(dilations) // sparsity_factor + 1
         # If there are only a small number of tests, just select them all
         if sparsity < 13:
@@ -1159,18 +1179,8 @@
                 for d in sorted(list(dilations)):
                     if n % sparsity == 0:
                         # Determine the output shape
-                        oh = (
-                            ifm_shape[1]
-                            - filter_shape[1]
-                            - (filter_shape[1] - 1) * (d[0] - 1)
-                            + 2 * p[0]
-                        ) // s[0] + 1
-                        ow = (
-                            ifm_shape[2]
-                            - filter_shape[2]
-                            - (filter_shape[2] - 1) * (d[1] - 1)
-                            + 2 * p[1]
-                        ) // s[1] + 1
+                        oh = (ifm_shape[1] - 1) * s[0] - p[0] - p[1] + filter_shape[1]
+                        ow = (ifm_shape[2] - 1) * s[1] - p[2] - p[3] + filter_shape[2]
                         os = [ifm_shape[0], oh, ow, filter_shape[0]]
                         arg_list.append(
                             (
@@ -1231,7 +1241,9 @@
         # Generate comprehensive argument lists
         p_vals = [x for x in range(0, testGen.args.max_pooling_padding + 1)]
         paddings = {x for x in itertools.product(*([p_vals] * 4))}
-        s_vals = [x for x in range(1, testGen.args.max_pooling_stride + 1)]
+        # Stride must be greater than 1 to force non-integer error
+        startStride = 1 if error_name != ErrorIf.PoolingOutputShapeNonInteger else 2
+        s_vals = [x for x in range(startStride, testGen.args.max_pooling_stride + 1)]
         strides = {x for x in itertools.product(*([s_vals] * 2))}
         k_vals = [x for x in range(2, testGen.args.max_pooling_kernel + 1)]
         kernels = {x for x in itertools.product(*([k_vals] * 2))}
@@ -1239,8 +1251,10 @@
         if testGen.args.oversize:
             # add some oversize argument values
             bigStride = 7
-            strides.update({x for x in itertools.product(*([[1, bigStride]] * 2))})
-            bigKernel = 6
+            strides.update(
+                {x for x in itertools.product(*([[startStride, bigStride]] * 2))}
+            )
+            bigKernel = 9
             kernels.update({x for x in itertools.product(*([[2, bigKernel]] * 2))})
             if max(shape) < 64:
                 # padding must be less than the kernel size
@@ -1289,16 +1303,27 @@
                         and (shape[1] + p[0] + p[1]) > k[0]
                         and (shape[2] + p[2] + p[3]) > k[1]
                     ):
-                        arg_list.append(
-                            (
-                                "st{}_kern{}_pad{}".format(
-                                    "".join([str(x) for x in s]),
-                                    "".join([str(x) for x in k]),
-                                    "".join([str(x) for x in p]),
-                                ),
-                                [s, p, k],
+                        remainder_h = (shape[1] + p[0] + p[1] - k[0]) % s[0]
+                        remainder_w = (shape[2] + p[2] + p[3] - k[1]) % s[1]
+                        if (
+                            # the parameters must produce integer exact output
+                            error_name != ErrorIf.PoolingOutputShapeNonInteger
+                            and remainder_h == 0
+                            and remainder_w == 0
+                        ) or (
+                            error_name == ErrorIf.PoolingOutputShapeNonInteger
+                            and (remainder_h != 0 or remainder_w != 0)
+                        ):
+                            arg_list.append(
+                                (
+                                    "st{}_kern{}_pad{}".format(
+                                        "".join([str(x) for x in s]),
+                                        "".join([str(x) for x in k]),
+                                        "".join([str(x) for x in p]),
+                                    ),
+                                    [s, p, k],
+                                )
                             )
-                        )
                     n += 1
 
         return arg_list
diff --git a/verif/generator/tosa_error_if.py b/verif/generator/tosa_error_if.py
index caf63e3..e7e758f 100644
--- a/verif/generator/tosa_error_if.py
+++ b/verif/generator/tosa_error_if.py
@@ -42,6 +42,9 @@
     PadSmallerZero = "PadSmallerZero"
     PadLargerEqualKernel = "PadLargerEqualKernel"
     PoolingOutputShapeMismatch = "PoolingOutputShapeMismatch"
+    PoolingOutputShapeNonInteger = "PoolingOutputShapeNonInteger"
+    ConvOutputShapeMismatch = "ConvOutputShapeMismatch"
+    ConvOutputShapeNonInteger = "ConvOutputShapeNonInteger"
     ScaleNotTrue = "ScaleNotTrue"
     ScaleTrue = "ScaleTrue"
     TensorSizeInputOutputMismatch = "TensorSizeInputOutputMismatch"
@@ -1226,6 +1229,20 @@
         return info_dict
 
     @staticmethod
+    def checkPoolingParams(kernel, stride, pad):
+        return (
+            min(kernel) >= 1
+            and min(stride) >= 1
+            and min(pad) >= 0
+            and not (
+                pad[0] >= kernel[0]
+                or pad[1] >= kernel[0]
+                or pad[2] >= kernel[1]
+                or pad[3] >= kernel[1]
+            )
+        )
+
+    @staticmethod
     def evPoolingOutputShapeMismatch(check=False, **kwargs):
         error_name = ErrorIf.PoolingOutputShapeMismatch
         param_reqs = {"rank": None, "dtype": None, "shape": None}
@@ -1252,25 +1269,11 @@
 
             # calculate correct height, width dimensions
             if stride_x != 0 and stride_y != 0:
-                y_correct = (
-                    IH + pad_top + pad_bottom + stride_y - kernel_y
-                ) // stride_y
-                x_correct = (
-                    IW + pad_left + pad_right + stride_x - kernel_x
-                ) // stride_x
+                y_correct = ((IH + pad_top + pad_bottom - kernel_y) // stride_y) + 1
+                x_correct = ((IW + pad_left + pad_right - kernel_x) // stride_x) + 1
 
             # ensure parameters are valid
-            params_valid = (
-                min(kernel) >= 1
-                and min(stride) >= 1
-                and min(pad) >= 0
-                and not (
-                    pad[0] >= kernel[0]
-                    or pad[1] >= kernel[0]
-                    or pad[2] >= kernel[1]
-                    or pad[3] >= kernel[1]
-                )
-            )
+            params_valid = TosaErrorValidator.checkPoolingParams(kernel, stride, pad)
 
             if params_valid and (OH != y_correct or OW != x_correct):
                 error_result = True
@@ -1284,6 +1287,165 @@
         return info_dict
 
     @staticmethod
+    def evPoolingOutputShapeNonInteger(check=False, **kwargs):
+        error_name = ErrorIf.PoolingOutputShapeNonInteger
+        param_reqs = {"rank": None, "dtype": None, "shape": None}
+        error_result = False
+        error_reason = "Parameters do not yield exact integer output dimensions"
+
+        if check:
+            pad = kwargs["pad"]
+            pad_top, pad_bottom, pad_left, pad_right = pad[0], pad[1], pad[2], pad[3]
+
+            kernel = kwargs["kernel"]
+            kernel_y, kernel_x = kernel[0], kernel[1]
+
+            input_shape = kwargs["input_shape"]
+            IH, IW = input_shape[1], input_shape[2]
+
+            stride = kwargs["stride"]
+            stride_y, stride_x = stride[0], stride[1]
+
+            # calculate remainder of height, width dimensions
+            if stride_x != 0 and stride_y != 0:
+                y_remainder = (IH + pad_top + pad_bottom - kernel_y) % stride_y
+                x_remainder = (IW + pad_left + pad_right - kernel_x) % stride_x
+
+            # ensure parameters are valid
+            params_valid = TosaErrorValidator.checkPoolingParams(kernel, stride, pad)
+            if params_valid and (y_remainder != 0 or x_remainder != 0):
+                error_result = True
+
+        info_dict = {
+            "error_name": error_name,
+            "error_result": error_result,
+            "error_reason": error_reason,
+            "param_reqs": param_reqs,
+        }
+        return info_dict
+
+    @staticmethod
+    def checkConvParams(weight_shape, stride, pad, dilation):
+        return (
+            # Check kernel sizes
+            min(weight_shape[1:-1]) >= 1
+            and min(stride) >= 1
+            and min(pad) >= 0
+            and (dilation is None or min(dilation) >= 1)
+        )
+
+    @staticmethod
+    def evConvOutputShapeMismatch(check=False, **kwargs):
+        error_name = ErrorIf.ConvOutputShapeMismatch
+        param_reqs = {"rank": None, "dtype": None, "shape": None}
+        error_result = False
+        error_reason = (
+            "Mismatch between output shape provided and expected output shape"
+        )
+
+        if check:
+            op = kwargs["op"]
+            pad = kwargs["pad"]
+            weight_shape = kwargs["weight_shape"]
+            input_shape = kwargs["input_shape"]
+            output_shape = kwargs["output_shape"]
+            dilation = kwargs["dilation"] if op["op"] != Op.TRANSPOSE_CONV2D else None
+            stride = kwargs["stride"]
+
+            kernel_offset = 0 if op["op"] == Op.DEPTHWISE_CONV2D else 1
+
+            # calculate correct dimensions
+            dims_correct = []
+            if min(stride) > 0:
+                for index in range(len(stride)):
+                    pad_offset = index * 2
+                    if op["op"] == Op.TRANSPOSE_CONV2D:
+                        dims_correct.append(
+                            (input_shape[index + 1] - 1) * stride[index]
+                            - pad[pad_offset]
+                            - pad[pad_offset + 1]
+                            + weight_shape[index + kernel_offset]
+                        )
+                    else:
+                        dims_correct.append(
+                            (
+                                input_shape[index + 1]
+                                - 1
+                                + pad[pad_offset]
+                                + pad[pad_offset + 1]
+                                - (weight_shape[index + kernel_offset] - 1)
+                                * dilation[index]
+                            )
+                            // stride[index]
+                            + 1
+                        )
+
+            # ensure parameters are valid
+            params_valid = TosaErrorValidator.checkConvParams(
+                weight_shape, stride, pad, dilation
+            )
+
+            if params_valid and output_shape[1:-1] != dims_correct:
+                error_result = True
+
+        info_dict = {
+            "error_name": error_name,
+            "error_result": error_result,
+            "error_reason": error_reason,
+            "param_reqs": param_reqs,
+        }
+        return info_dict
+
+    @staticmethod
+    def evConvOutputShapeNonInteger(check=False, **kwargs):
+        error_name = ErrorIf.ConvOutputShapeNonInteger
+        param_reqs = {"rank": None, "dtype": None, "shape": None}
+        error_result = False
+        error_reason = "Parameters do not yield exact integer output dimensions"
+
+        if check:
+            op = kwargs["op"]
+            pad = kwargs["pad"]
+            weight_shape = kwargs["weight_shape"]
+            input_shape = kwargs["input_shape"]
+            dilation = kwargs["dilation"]
+            stride = kwargs["stride"]
+
+            kernel_offset = 0 if op["op"] == Op.DEPTHWISE_CONV2D else 1
+
+            # calculate correct height, width dimensions
+            remainders = []
+            if min(stride) > 0:
+                for index in range(len(stride)):
+                    pad_offset = index * 2
+                    remainders.append(
+                        (
+                            input_shape[index + 1]
+                            - 1
+                            + pad[pad_offset]
+                            + pad[pad_offset + 1]
+                            - (weight_shape[index + kernel_offset] - 1)
+                            * dilation[index]
+                        )
+                        % stride[index]
+                    )
+
+            # ensure parameters are valid
+            params_valid = TosaErrorValidator.checkConvParams(
+                weight_shape, stride, pad, dilation
+            )
+            if params_valid and max(remainders) > 0:
+                error_result = True
+
+        info_dict = {
+            "error_name": error_name,
+            "error_result": error_result,
+            "error_reason": error_reason,
+            "param_reqs": param_reqs,
+        }
+        return info_dict
+
+    @staticmethod
     def evArgmaxOutputShapeMismatch(check=False, **kwargs):
         error_name = ErrorIf.ArgmaxOutputShapeMismatch
         param_reqs = {"rank": [2, 4], "dtype": None, "shape": None}
diff --git a/verif/generator/tosa_test_gen.py b/verif/generator/tosa_test_gen.py
index 38365d0..7c2b9de 100644
--- a/verif/generator/tosa_test_gen.py
+++ b/verif/generator/tosa_test_gen.py
@@ -630,6 +630,8 @@
             stride=strides,
             dilation=dilations,
             input_shape=ifm.shape,
+            weight_shape=filter.shape,
+            output_shape=result_tens.shape,
         ):
             return None
 
@@ -692,6 +694,8 @@
             stride=strides,
             dilation=dilations,
             input_shape=ifm.shape,
+            weight_shape=filter.shape,
+            output_shape=result_tens.shape,
         ):
             return None
 
@@ -715,7 +719,7 @@
         error_name=None,
         qinfo=None,
     ):
-        assert len(outpad) == 2
+        assert len(outpad) == 4
         result_tens = OutputShaper.transposeConv2DOp(
             self.ser, self.rng, ifm, output_shape, error_name
         )
@@ -753,8 +757,9 @@
             output_list=output_list,
             pad=outpad,
             stride=stride,
-            dilation=dilation,
             input_shape=ifm.shape,
+            weight_shape=filter.shape,
+            output_shape=result_tens.shape,
         ):
             return None
 
@@ -816,6 +821,8 @@
             stride=strides,
             dilation=dilations,
             input_shape=ifm.shape,
+            weight_shape=filter.shape,
+            output_shape=result_tens.shape,
         ):
             return None
 
@@ -2393,6 +2400,7 @@
                 TosaErrorValidator.evOutputZeroPointNotZero,
                 TosaErrorValidator.evPadLargerEqualKernel,
                 TosaErrorValidator.evPoolingOutputShapeMismatch,
+                TosaErrorValidator.evPoolingOutputShapeNonInteger,
             ),
         },
         # Templated operator.  Filled in by createDynamicOpLists
@@ -2420,6 +2428,8 @@
                 TosaErrorValidator.evStrideSmallerOne,
                 TosaErrorValidator.evDilationSmallerOne,
                 TosaErrorValidator.evWrongRank,
+                TosaErrorValidator.evConvOutputShapeMismatch,
+                TosaErrorValidator.evConvOutputShapeNonInteger,
             ),
             "template": True,
         },
@@ -2448,6 +2458,8 @@
                 TosaErrorValidator.evStrideSmallerOne,
                 TosaErrorValidator.evDilationSmallerOne,
                 TosaErrorValidator.evWrongRank,
+                TosaErrorValidator.evConvOutputShapeMismatch,
+                TosaErrorValidator.evConvOutputShapeNonInteger,
             ),
             "template": True,
         },
@@ -2477,6 +2489,8 @@
                 TosaErrorValidator.evStrideSmallerOne,
                 TosaErrorValidator.evDilationSmallerOne,
                 TosaErrorValidator.evWrongRank,
+                TosaErrorValidator.evConvOutputShapeMismatch,
+                TosaErrorValidator.evConvOutputShapeNonInteger,
             ),
             "template": True,
         },
@@ -2546,6 +2560,7 @@
                 TosaErrorValidator.evWrongOutputList,
                 TosaErrorValidator.evPadLargerEqualKernel,
                 TosaErrorValidator.evPoolingOutputShapeMismatch,
+                TosaErrorValidator.evPoolingOutputShapeNonInteger,
             ),
         },
         # Templated operator.  Filled in by createDynamicOpLists
@@ -2574,8 +2589,8 @@
                 TosaErrorValidator.evWeightZeroPointNotZero,
                 TosaErrorValidator.evPadSmallerZero,
                 TosaErrorValidator.evStrideSmallerOne,
-                TosaErrorValidator.evDilationSmallerOne,
                 TosaErrorValidator.evWrongRank,
+                TosaErrorValidator.evConvOutputShapeMismatch,
             ),
             "template": True,
         },
@@ -3887,30 +3902,30 @@
         # Filter: OHWI
         # OFM:    NHWC
 
-        if len(padding) == 2:
-            # Expand padding to 4 parameters in the case of transpose_conv2d
-            # From H,W to T,B,L,R
-            padding = [padding[0], padding[0], padding[1], padding[1]]
-
         h = (
             ifm.shape[1]
-            - filter.shape[1]
-            - (filter.shape[1] - 1) * (dilations[0] - 1)
+            - 1
             + padding[0]
             + padding[1]
+            - (filter.shape[1] - 1) * dilations[0]
         ) // strides[0] + 1
 
         w = (
             ifm.shape[2]
-            - filter.shape[2]
-            - (filter.shape[2] - 1) * (dilations[1] - 1)
+            - 1
             + padding[2]
             + padding[3]
+            - (filter.shape[2] - 1) * dilations[1]
         ) // strides[1] + 1
 
-        # Avoid illegal dimensions, which can be generated in error_if tests
-        h = max(h, 1)
-        w = max(w, 1)
+        if error_name == ErrorIf.ConvOutputShapeMismatch:
+            choices = [1, 2, 3]
+            change = rng.choice(choices)
+            # increment in multiples of stride to not hit non-integer error case
+            if change in [1, 3]:
+                h = h + (rng.choice(choices) * strides[0])
+            if change in [2, 3]:
+                w = w + (rng.choice(choices) * strides[1])
 
         ofm_shape = [ifm.shape[0], h, w, filter.shape[0]]
 
@@ -3941,32 +3956,38 @@
 
         d = (
             ifm.shape[1]
-            - filter.shape[1]
-            - (filter.shape[1] - 1) * (dilations[0] - 1)
+            - 1
             + padding[0]
             + padding[1]
+            - (filter.shape[1] - 1) * dilations[0]
         ) // strides[0] + 1
 
         h = (
             ifm.shape[2]
-            - filter.shape[2]
-            - (filter.shape[2] - 1) * (dilations[1] - 1)
+            - 1
             + padding[2]
             + padding[3]
+            - (filter.shape[2] - 1) * dilations[1]
         ) // strides[1] + 1
 
         w = (
             ifm.shape[3]
-            - filter.shape[3]
-            - (filter.shape[3] - 1) * (dilations[2] - 1)
+            - 1
             + padding[4]
             + padding[5]
+            - (filter.shape[3] - 1) * dilations[2]
         ) // strides[2] + 1
 
-        # Avoid illegal dimensions, which can be generated in error_if tests
-        d = max(d, 1)
-        h = max(h, 1)
-        w = max(w, 1)
+        if error_name == ErrorIf.ConvOutputShapeMismatch:
+            choices = [1, 2, 3, 4]
+            change = rng.choice(choices)
+            # increment in multiples of stride to not hit non-integer error case
+            if change in [1, 4]:
+                d = d + (rng.choice(choices) * strides[0])
+            if change in [2, 4]:
+                h = h + (rng.choice(choices) * strides[1])
+            if change in [3, 4]:
+                w = w + (rng.choice(choices) * strides[2])
 
         ofm_shape = [ifm.shape[0], d, h, w, filter.shape[0]]
 
@@ -3995,25 +4016,31 @@
         # IFM:    NHWC
         # Filter: HWCM
         # OFM:    NHW C*M
+
         h = (
             ifm.shape[1]
-            - filter.shape[0]
-            - (filter.shape[0] - 1) * (dilations[0] - 1)
+            - 1
             + padding[0]
             + padding[1]
+            - (filter.shape[0] - 1) * dilations[0]
         ) // strides[0] + 1
 
         w = (
             ifm.shape[2]
-            - filter.shape[1]
-            - (filter.shape[1] - 1) * (dilations[1] - 1)
+            - 1
             + padding[2]
             + padding[3]
+            - (filter.shape[1] - 1) * dilations[1]
         ) // strides[1] + 1
 
-        # Avoid illegal dimensions, which can be generated in error_if tests
-        h = max(h, 1)
-        w = max(w, 1)
+        if error_name == ErrorIf.ConvOutputShapeMismatch:
+            choices = [1, 2, 3]
+            change = rng.choice(choices)
+            # increment in multiples of stride to not hit non-integer error case
+            if change in [1, 3]:
+                h = h + (rng.choice(choices) * strides[0])
+            if change in [2, 3]:
+                w = w + (rng.choice(choices) * strides[1])
 
         ofm_shape = [ifm.shape[0], h, w, filter.shape[2] * filter.shape[3]]
 
@@ -4043,14 +4070,17 @@
             h = 1
             w = 1
         else:
-            h = (ifm.shape[1] + pad[0] + pad[1] + stride[0] - kernel[0]) // stride[0]
-            w = (ifm.shape[2] + pad[2] + pad[3] + stride[1] - kernel[1]) // stride[1]
+            h = (ifm.shape[1] + pad[0] + pad[1] - kernel[0]) // stride[0] + 1
+            w = (ifm.shape[2] + pad[2] + pad[3] - kernel[1]) // stride[1] + 1
 
         if error_name == ErrorIf.PoolingOutputShapeMismatch:
-            choices = [1, 2, 3, 4, 5]
-            h = h + rng.choice(choices)
-            w = w + rng.choice(choices)
-
+            choices = [1, 2, 3]
+            change = rng.choice(choices)
+            # increment in multiples of stride to not hit non-integer error case
+            if change in [1, 3]:
+                h = h + (rng.choice(choices) * stride[0])
+            if change in [2, 3]:
+                w = w + (rng.choice(choices) * stride[1])
         ofm_shape = [ifm.shape[0], h, w, ifm.shape[3]]
 
         if error_name == ErrorIf.WrongOutputType:
@@ -4468,6 +4498,14 @@
 
     @staticmethod
     def transposeConv2DOp(ser, rng, ifm, output_shape, error_name=None):
+        if error_name == ErrorIf.ConvOutputShapeMismatch:
+            choices = [1, 2, 3]
+            change = rng.choice(choices)
+            if change in [1, 3]:
+                output_shape[1] = output_shape[1] + rng.choice(choices)
+            if change in [2, 3]:
+                output_shape[2] = output_shape[2] + rng.choice(choices)
+
         if ifm.dtype == DType.INT8:
             out_dtype = DType.INT32
         elif ifm.dtype == DType.INT16: