/ test / func_blocks / fu / fpu / test_fpu_error.py
test_fpu_error.py
  1  from coreblocks.func_blocks.fu.fpu.fpu_error_module import *
  2  from coreblocks.func_blocks.fu.fpu.fpu_common import (
  3      RoundingModes,
  4      FPUParams,
  5      Errors,
  6  )
  7  from transactron import TModule
  8  from transactron.lib import AdapterTrans
  9  from parameterized import parameterized
 10  from transactron.testing import *
 11  from amaranth import *
 12  
 13  
 14  class TestFPUError(TestCaseWithSimulator):
 15      class FPUErrorModule(Elaboratable):
 16          def __init__(self, params: FPUParams):
 17              self.params = params
 18  
 19          def elaborate(self, platform):
 20              m = TModule()
 21              m.submodules.fpue = fpue = self.fpu_error_module = FPUErrorModule(fpu_params=self.params)
 22              m.submodules.error_checking = self.error_checking_request_adapter = TestbenchIO(
 23                  AdapterTrans.create(fpue.error_checking_request)
 24              )
 25              return m
 26  
 27      class HelpValues:
 28          def __init__(self, params: FPUParams):
 29              self.params = params
 30              self.implicit_bit = 2 ** (self.params.sig_width - 1)
 31              self.max_exp = (2**self.params.exp_width) - 1
 32              self.max_norm_exp = (2**self.params.exp_width) - 2
 33              self.not_max_norm_exp = (2**self.params.exp_width) - 3
 34              self.max_sig = (2**params.sig_width) - 1
 35              self.not_max_norm_sig = 1 << (self.params.sig_width - 1) | 1
 36              self.not_max_norm_even_sig = 1 << (self.params.sig_width - 1)
 37              self.sub_norm_sig = 3
 38              self.min_norm_sig = 1 << (self.params.sig_width - 1)
 39              self.max_sub_norm_sig = (2 ** (self.params.sig_width - 1)) - 1
 40              self.qnan = 3 << (self.params.sig_width - 2) | 1
 41  
 42      params = FPUParams(sig_width=24, exp_width=8)
 43      help_values = HelpValues(params)
 44  
 45      @parameterized.expand([(params, help_values)])
 46      def test_special_cases(self, params: FPUParams, help_values: HelpValues):
 47          fpue = TestFPUError.FPUErrorModule(params)
 48  
 49          async def other_cases_test(sim: TestbenchContext):
 50              test_cases = [
 51                  # No errors
 52                  {
 53                      "sign": 0,
 54                      "sig": help_values.not_max_norm_even_sig,
 55                      "exp": help_values.not_max_norm_exp,
 56                      "inexact": 0,
 57                      "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY,
 58                      "invalid_operation": 0,
 59                      "division_by_zero": 0,
 60                      "input_inf": 0,
 61                  },
 62                  # inexact
 63                  {
 64                      "sign": 0,
 65                      "sig": help_values.not_max_norm_even_sig,
 66                      "exp": help_values.not_max_norm_exp,
 67                      "inexact": 1,
 68                      "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY,
 69                      "invalid_operation": 0,
 70                      "division_by_zero": 0,
 71                      "input_inf": 0,
 72                  },
 73                  # underflow
 74                  {
 75                      "sign": 0,
 76                      "sig": help_values.sub_norm_sig,
 77                      "exp": 0,
 78                      "inexact": 1,
 79                      "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY,
 80                      "invalid_operation": 0,
 81                      "division_by_zero": 0,
 82                      "input_inf": 0,
 83                  },
 84                  # invalid operation
 85                  {
 86                      "sign": 0,
 87                      "sig": help_values.qnan,
 88                      "exp": help_values.max_exp,
 89                      "inexact": 1,
 90                      "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY,
 91                      "invalid_operation": 1,
 92                      "division_by_zero": 0,
 93                      "input_inf": 0,
 94                  },
 95                  # division by zero
 96                  {
 97                      "sign": 0,
 98                      "sig": help_values.implicit_bit,
 99                      "exp": help_values.max_exp,
100                      "inexact": 1,
101                      "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY,
102                      "invalid_operation": 0,
103                      "division_by_zero": 1,
104                      "input_inf": 0,
105                  },
106                  # overflow but no round and sticky bits
107                  {
108                      "sign": 0,
109                      "sig": help_values.implicit_bit,
110                      "exp": help_values.max_exp,
111                      "inexact": 0,
112                      "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY,
113                      "invalid_operation": 0,
114                      "division_by_zero": 0,
115                      "input_inf": 0,
116                  },
117                  # tininess but no underflow
118                  {
119                      "sign": 0,
120                      "sig": help_values.sub_norm_sig,
121                      "exp": 0,
122                      "inexact": 0,
123                      "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY,
124                      "invalid_operation": 0,
125                      "division_by_zero": 0,
126                      "input_inf": 0,
127                  },
128                  # one of inputs was qnan
129                  {
130                      "sign": 0,
131                      "sig": help_values.qnan,
132                      "exp": help_values.max_exp,
133                      "inexact": 1,
134                      "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY,
135                      "invalid_operation": 0,
136                      "division_by_zero": 0,
137                      "input_inf": 0,
138                  },
139                  # one of inputs was inf
140                  {
141                      "sign": 1,
142                      "sig": help_values.implicit_bit,
143                      "exp": help_values.max_exp,
144                      "inexact": 1,
145                      "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY,
146                      "invalid_operation": 0,
147                      "division_by_zero": 0,
148                      "input_inf": 1,
149                  },
150                  # subnormal number become normalized after rounding
151                  {
152                      "sign": 1,
153                      "sig": help_values.min_norm_sig,
154                      "exp": 0,
155                      "inexact": 1,
156                      "rounding_mode": RoundingModes.ROUND_NEAREST_AWAY,
157                      "invalid_operation": 0,
158                      "division_by_zero": 0,
159                      "input_inf": 0,
160                  },
161              ]
162  
163              expected_results = [
164                  # No errors
165                  {"sign": 0, "sig": help_values.not_max_norm_even_sig, "exp": help_values.not_max_norm_exp, "errors": 0},
166                  # inexact
167                  {
168                      "sign": 0,
169                      "sig": help_values.not_max_norm_even_sig,
170                      "exp": help_values.not_max_norm_exp,
171                      "errors": Errors.INEXACT,
172                  },
173                  # underflow
174                  {"sign": 0, "sig": help_values.sub_norm_sig, "exp": 0, "errors": Errors.UNDERFLOW | Errors.INEXACT},
175                  # invalid operation
176                  {"sign": 0, "sig": help_values.qnan, "exp": help_values.max_exp, "errors": Errors.INVALID_OPERATION},
177                  # division by zero
178                  {
179                      "sign": 0,
180                      "sig": help_values.implicit_bit,
181                      "exp": help_values.max_exp,
182                      "errors": Errors.DIVISION_BY_ZERO,
183                  },
184                  # overflow but no round and sticky bits
185                  {
186                      "sign": 0,
187                      "sig": help_values.implicit_bit,
188                      "exp": help_values.max_exp,
189                      "errors": Errors.INEXACT | Errors.OVERFLOW,
190                  },
191                  # tininess but no underflow
192                  {"sign": 0, "sig": help_values.sub_norm_sig, "exp": 0, "errors": 0},
193                  # one of inputs was qnan
194                  {"sign": 0, "sig": help_values.qnan, "exp": help_values.max_exp, "errors": 0},
195                  # one of inputs was inf
196                  {"sign": 1, "sig": help_values.implicit_bit, "exp": help_values.max_exp, "errors": 0},
197                  # subnormal number become normalized after rounding
198                  {"sign": 1, "sig": help_values.min_norm_sig, "exp": 1, "errors": Errors.INEXACT},
199              ]
200              for i in range(len(test_cases)):
201                  resp = await fpue.error_checking_request_adapter.call(sim, test_cases[i])
202                  assert resp.sign == expected_results[i]["sign"]
203                  assert resp.exp == expected_results[i]["exp"]
204                  assert resp.sig == expected_results[i]["sig"]
205                  assert resp.errors == expected_results[i]["errors"]
206  
207          async def test_process(sim: TestbenchContext):
208              await other_cases_test(sim)
209  
210          with self.run_simulation(fpue) as sim:
211              sim.add_testbench(test_process)
212  
213      @parameterized.expand(
214          [
215              (
216                  params,
217                  help_values,
218                  RoundingModes.ROUND_NEAREST_EVEN,
219                  help_values.implicit_bit,
220                  help_values.max_exp,
221                  help_values.implicit_bit,
222                  help_values.max_exp,
223              ),
224              (
225                  params,
226                  help_values,
227                  RoundingModes.ROUND_NEAREST_AWAY,
228                  help_values.implicit_bit,
229                  help_values.max_exp,
230                  help_values.implicit_bit,
231                  help_values.max_exp,
232              ),
233              (
234                  params,
235                  help_values,
236                  RoundingModes.ROUND_UP,
237                  help_values.implicit_bit,
238                  help_values.max_exp,
239                  help_values.max_sig,
240                  help_values.max_norm_exp,
241              ),
242              (
243                  params,
244                  help_values,
245                  RoundingModes.ROUND_DOWN,
246                  help_values.max_sig,
247                  help_values.max_norm_exp,
248                  help_values.implicit_bit,
249                  help_values.max_exp,
250              ),
251              (
252                  params,
253                  help_values,
254                  RoundingModes.ROUND_ZERO,
255                  help_values.max_sig,
256                  help_values.max_norm_exp,
257                  help_values.max_sig,
258                  help_values.max_norm_exp,
259              ),
260          ]
261      )
262      def test_rounding(
263          self,
264          params: FPUParams,
265          help_values: HelpValues,
266          rm: RoundingModes,
267          plus_overflow_sig: int,
268          plus_overflow_exp: int,
269          minus_overflow_sig: int,
270          minus_overflow_exp: int,
271      ):
272          fpue = TestFPUError.FPUErrorModule(params)
273  
274          async def one_rounding_mode_test(sim: TestbenchContext):
275              test_cases = [
276                  # overflow detection
277                  {
278                      "sign": 0,
279                      "sig": help_values.implicit_bit,
280                      "exp": help_values.max_exp,
281                      "rounding_mode": rm,
282                      "inexact": 0,
283                      "invalid_operation": 0,
284                      "division_by_zero": 0,
285                      "input_inf": 0,
286                  },
287                  {
288                      "sign": 1,
289                      "sig": help_values.implicit_bit,
290                      "exp": help_values.max_exp,
291                      "rounding_mode": rm,
292                      "inexact": 0,
293                      "invalid_operation": 0,
294                      "division_by_zero": 0,
295                      "input_inf": 0,
296                  },
297              ]
298              expected_results = [
299                  # overflow detection
300                  {
301                      "sign": 0,
302                      "sig": plus_overflow_sig,
303                      "exp": plus_overflow_exp,
304                      "errors": Errors.INEXACT | Errors.OVERFLOW,
305                  },
306                  {
307                      "sign": 1,
308                      "sig": minus_overflow_sig,
309                      "exp": minus_overflow_exp,
310                      "errors": Errors.INEXACT | Errors.OVERFLOW,
311                  },
312              ]
313  
314              for i in range(len(test_cases)):
315                  resp = await fpue.error_checking_request_adapter.call(sim, test_cases[i])
316                  assert resp["sign"] == expected_results[i]["sign"]
317                  assert resp["exp"] == expected_results[i]["exp"]
318                  assert resp["sig"] == expected_results[i]["sig"]
319                  assert resp["errors"] == expected_results[i]["errors"]
320  
321          async def test_process(sim: TestbenchContext):
322              await one_rounding_mode_test(sim)
323  
324          with self.run_simulation(fpue) as sim:
325              sim.add_testbench(test_process)