1# SPDX-FileCopyrightText: Copyright 2010-2024 Arm Limited and/or its affiliates <open-source-office@arm.com>
2#
3# SPDX-License-Identifier: Apache-2.0
4#
5# Licensed under the Apache License, Version 2.0 (the License); you may
6# not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an AS IS BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17from test_settings import TestSettings
18
19import tensorflow as tf
20import numpy as np
21import tf_keras as keras
22
23
24class AddMulSettings(TestSettings):
25
26    def __init__(self,
27                 dataset,
28                 testtype,
29                 regenerate_weights,
30                 regenerate_input,
31                 regenerate_biases,
32                 schema_file,
33                 channels=1,
34                 x_in=4,
35                 y_in=4,
36                 decimal_input=6,
37                 randmin=TestSettings.INT8_MIN,
38                 randmax=TestSettings.INT8_MAX,
39                 out_activation_min=TestSettings.INT8_MIN,
40                 out_activation_max=TestSettings.INT8_MAX,
41                 int16xint8=False,
42                 interpreter="tensorflow"):
43        super().__init__(dataset,
44                         testtype,
45                         regenerate_weights,
46                         regenerate_input,
47                         regenerate_biases,
48                         schema_file,
49                         in_ch=channels,
50                         out_ch=channels,
51                         x_in=x_in,
52                         y_in=y_in,
53                         w_x=1,
54                         w_y=1,
55                         stride_x=1,
56                         stride_y=1,
57                         pad=False,
58                         randmin=randmin,
59                         randmax=randmax,
60                         batches=1,
61                         generate_bias=False,
62                         relu6=False,
63                         out_activation_min=out_activation_min,
64                         out_activation_max=out_activation_max,
65                         int16xint8=int16xint8,
66                         interpreter=interpreter)
67
68        self.x_input = self.x_output = x_in
69        self.y_input = self.y_output = y_in
70        self.decimal_input = decimal_input
71
72        self.left_shift = 15 if self.is_int16xint8 else 20
73
74    def generate_data(self, input_data1=None, input_data2=None) -> None:
75        input_shape = (1, self.y_input, self.x_input, self.input_ch)
76
77        input_data1 = self.get_randomized_data(list(input_shape),
78                                               self.inputs_table_file,
79                                               regenerate=self.regenerate_new_input,
80                                               decimals=self.decimal_input)
81        input_data2 = self.get_randomized_data(list(input_shape),
82                                               self.kernel_table_file,
83                                               regenerate=self.regenerate_new_weights,
84                                               decimals=self.decimal_input)
85
86        if self.is_int16xint8:
87            inttype = "int16_t"
88            inttype_tf = tf.int16
89        else:
90            inttype = "int8_t"
91            inttype_tf = tf.int8
92
93        # Create a one-layer functional Keras model as add/mul cannot use a sequntial Keras model.
94        input1 = keras.layers.Input(shape=input_shape[1:])
95        input2 = keras.layers.Input(shape=input_shape[1:])
96        if self.test_type == 'add':
97            layer = keras.layers.Add()([input1, input2])
98        elif self.test_type == 'mul':
99            layer = keras.layers.Multiply()([input1, input2])
100        else:
101            raise RuntimeError("Wrong test type")
102        out = keras.layers.Lambda(function=lambda x: x)(layer)
103        model = keras.models.Model(inputs=[input1, input2], outputs=out)
104
105        interpreter = self.convert_and_interpret(model, inttype_tf)
106
107        input_details = interpreter.get_input_details()
108        interpreter.set_tensor(input_details[0]["index"], tf.cast(input_data1, inttype_tf))
109        interpreter.set_tensor(input_details[1]["index"], tf.cast(input_data2, inttype_tf))
110
111        # Calculate multipliers, shifts and offsets.
112        (input1_scale, self.input1_zero_point) = input_details[0]['quantization']
113        (input2_scale, self.input2_zero_point) = input_details[1]['quantization']
114        self.input1_zero_point = -self.input1_zero_point
115        self.input2_zero_point = -self.input2_zero_point
116        double_max_input_scale = max(input1_scale, input2_scale) * 2
117        (self.input1_mult, self.input1_shift) = self.quantize_scale(input1_scale / double_max_input_scale)
118        (self.input2_mult, self.input2_shift) = self.quantize_scale(input2_scale / double_max_input_scale)
119
120        if self.test_type == 'add':
121            actual_output_scale = double_max_input_scale / ((1 << self.left_shift) * self.output_scale)
122        elif self.test_type == 'mul':
123            actual_output_scale = input1_scale * input2_scale / self.output_scale
124        (self.output_mult, self.output_shift) = self.quantize_scale(actual_output_scale)
125
126        # Generate reference.
127        interpreter.invoke()
128        output_details = interpreter.get_output_details()
129        output_data = interpreter.get_tensor(output_details[0]["index"])
130        self.generate_c_array("input1", input_data1, datatype=inttype)
131        self.generate_c_array("input2", input_data2, datatype=inttype)
132        self.generate_c_array(self.output_data_file_prefix,
133                              np.clip(output_data, self.out_activation_min, self.out_activation_max),
134                              datatype=inttype)
135
136        self.write_c_config_header()
137        self.write_c_header_wrapper()
138
139    def write_c_config_header(self) -> None:
140        super().write_c_config_header(write_common_parameters=False)
141
142        filename = self.config_data
143        filepath = self.headers_dir + filename
144        prefix = self.testdataset.upper()
145
146        with open(filepath, "a") as f:
147            f.write("#define {}_DST_SIZE {}\n".format(prefix,
148                                                      self.batches * self.y_input * self.x_input * self.input_ch))
149            f.write("#define {}_OUT_ACTIVATION_MIN {}\n".format(prefix, self.out_activation_min))
150            f.write("#define {}_OUT_ACTIVATION_MAX {}\n".format(prefix, self.out_activation_max))
151            f.write("#define {}_INPUT1_OFFSET {}\n".format(prefix, self.input1_zero_point))
152            f.write("#define {}_INPUT2_OFFSET {}\n".format(prefix, self.input2_zero_point))
153            f.write("#define {}_OUTPUT_MULT {}\n".format(prefix, self.output_mult))
154            f.write("#define {}_OUTPUT_SHIFT {}\n".format(prefix, self.output_shift))
155            f.write("#define {}_OUTPUT_OFFSET {}\n".format(prefix, self.output_zero_point))
156            if self.test_type == 'add':
157                f.write("#define {}_LEFT_SHIFT {}\n".format(prefix, self.left_shift))
158                f.write("#define {}_INPUT1_SHIFT {}\n".format(prefix, self.input1_shift))
159                f.write("#define {}_INPUT2_SHIFT {}\n".format(prefix, self.input2_shift))
160                f.write("#define {}_INPUT1_MULT {}\n".format(prefix, self.input1_mult))
161                f.write("#define {}_INPUT2_MULT {}\n".format(prefix, self.input2_mult))
162