generate ilang for each part mul test
[ieee754fpu.git] / src / ieee754 / part_mul_add / test / test_multiply.py
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: LGPL-2.1-or-later
3 # See Notices.txt for copyright information
4
5 from ieee754.part_mul_add.multiply import \
6 (PartitionPoints, PartitionedAdder, AddReduce,
7 Mul8_16_32_64, OP_MUL_LOW, OP_MUL_SIGNED_HIGH,
8 OP_MUL_SIGNED_UNSIGNED_HIGH, OP_MUL_UNSIGNED_HIGH)
9 from nmigen import Signal, Module
10 from nmigen.back.pysim import Simulator, Delay, Tick, Passive
11 from nmigen.hdl.ast import Assign, Value
12 from typing import Any, Generator, List, Union, Optional, Tuple, Iterable
13 import unittest
14 from hashlib import sha256
15 import enum
16 import pdb
17 from nmigen.cli import verilog, rtlil
18
19
20 def create_ilang(dut, traces, test_name):
21 vl = rtlil.convert(dut, ports=traces)
22 with open("%s.il" % test_name, "w") as f:
23 f.write(vl)
24
25
26 def create_simulator(module: Any,
27 traces: List[Signal],
28 test_name: str) -> Simulator:
29 create_ilang(module, traces, test_name)
30 return Simulator(module,
31 vcd_file=open(test_name + ".vcd", "w"),
32 gtkw_file=open(test_name + ".gtkw", "w"),
33 traces=traces)
34
35
36 AsyncProcessCommand = Union[Delay, Tick, Passive, Assign, Value]
37 ProcessCommand = Optional[AsyncProcessCommand]
38 AsyncProcessGenerator = Generator[AsyncProcessCommand, Union[int, None], None]
39 ProcessGenerator = Generator[ProcessCommand, Union[int, None], None]
40
41
42 class TestPartitionPoints(unittest.TestCase):
43 def test(self) -> None:
44 module = Module()
45 width = 16
46 mask = Signal(width)
47 partition_point_10 = Signal()
48 partition_points = PartitionPoints({1: True,
49 5: False,
50 10: partition_point_10})
51 module.d.comb += mask.eq(partition_points.as_mask(width))
52 with create_simulator(module,
53 [mask, partition_point_10],
54 "partition_points") as sim:
55 def async_process() -> AsyncProcessGenerator:
56 self.assertEqual((yield partition_points[1]), True)
57 self.assertEqual((yield partition_points[5]), False)
58 yield partition_point_10.eq(0)
59 yield Delay(1e-6)
60 self.assertEqual((yield mask), 0xFFFD)
61 yield partition_point_10.eq(1)
62 yield Delay(1e-6)
63 self.assertEqual((yield mask), 0xFBFD)
64
65 sim.add_process(async_process)
66 sim.run()
67
68
69 class TestPartitionedAdder(unittest.TestCase):
70 def test(self) -> None:
71 width = 16
72 partition_nibbles = Signal()
73 partition_bytes = Signal()
74 module = PartitionedAdder(width,
75 {0x4: partition_nibbles,
76 0x8: partition_bytes | partition_nibbles,
77 0xC: partition_nibbles})
78 with create_simulator(module,
79 [partition_nibbles,
80 partition_bytes,
81 module.a,
82 module.b,
83 module.output],
84 "partitioned_adder") as sim:
85 def async_process() -> AsyncProcessGenerator:
86 def test_add(msg_prefix: str,
87 *mask_list: Tuple[int, ...]) -> Any:
88 for a, b in [(0x0000, 0x0000),
89 (0x1234, 0x1234),
90 (0xABCD, 0xABCD),
91 (0xFFFF, 0x0000),
92 (0x0000, 0x0000),
93 (0xFFFF, 0xFFFF),
94 (0x0000, 0xFFFF)]:
95 yield module.a.eq(a)
96 yield module.b.eq(b)
97 yield Delay(1e-6)
98 y = 0
99 for mask in mask_list:
100 y |= mask & ((a & mask) + (b & mask))
101 output = (yield module.output)
102 msg = f"{msg_prefix}: 0x{a:X} + 0x{b:X}" + \
103 f" => 0x{y:X} != 0x{output:X}"
104 self.assertEqual(y, output, msg)
105 yield partition_nibbles.eq(0)
106 yield partition_bytes.eq(0)
107 yield from test_add("16-bit", 0xFFFF)
108 yield partition_nibbles.eq(0)
109 yield partition_bytes.eq(1)
110 yield from test_add("8-bit", 0xFF00, 0x00FF)
111 yield partition_nibbles.eq(1)
112 yield partition_bytes.eq(0)
113 yield from test_add("4-bit", 0xF000, 0x0F00, 0x00F0, 0x000F)
114
115 sim.add_process(async_process)
116 sim.run()
117
118
119 class GenOrCheck(enum.Enum):
120 Generate = enum.auto()
121 Check = enum.auto()
122
123
124 class TestAddReduce(unittest.TestCase):
125 def calculate_input_values(self,
126 input_count: int,
127 key: int,
128 extra_keys: List[int] = []
129 ) -> (List[int], List[str]):
130 input_values = []
131 input_values_str = []
132 for i in range(input_count):
133 if key == 0:
134 value = 0
135 elif key == 1:
136 value = 0xFFFF
137 elif key == 2:
138 value = 0x0111
139 else:
140 hash_input = f"{input_count} {i} {key} {extra_keys}"
141 hash = sha256(hash_input.encode()).digest()
142 value = int.from_bytes(hash, byteorder="little")
143 value &= 0xFFFF
144 input_values.append(value)
145 input_values_str.append(f"0x{value:04X}")
146 return input_values, input_values_str
147
148 def subtest_value(self,
149 inputs: List[Signal],
150 module: AddReduce,
151 mask_list: List[int],
152 gen_or_check: GenOrCheck,
153 values: List[int]) -> AsyncProcessGenerator:
154 if gen_or_check == GenOrCheck.Generate:
155 for i, v in zip(inputs, values):
156 yield i.eq(v)
157 yield Delay(1e-6)
158 y = 0
159 for mask in mask_list:
160 v = 0
161 for value in values:
162 v += value & mask
163 y |= mask & v
164 output = (yield module.o.output)
165 if gen_or_check == GenOrCheck.Check:
166 self.assertEqual(y, output, f"0x{y:X} != 0x{output:X}")
167 yield Tick()
168
169 def subtest_key(self,
170 input_count: int,
171 inputs: List[Signal],
172 module: AddReduce,
173 key: int,
174 mask_list: List[int],
175 gen_or_check: GenOrCheck) -> AsyncProcessGenerator:
176 values, values_str = self.calculate_input_values(input_count, key)
177 if gen_or_check == GenOrCheck.Check:
178 with self.subTest(inputs=values_str):
179 yield from self.subtest_value(inputs,
180 module,
181 mask_list,
182 gen_or_check,
183 values)
184 else:
185 yield from self.subtest_value(inputs,
186 module,
187 mask_list,
188 gen_or_check,
189 values)
190
191 def subtest_run_sim(self,
192 input_count: int,
193 sim: Simulator,
194 partition_4: Signal,
195 partition_8: Signal,
196 inputs: List[Signal],
197 module: AddReduce,
198 delay_cycles: int) -> None:
199 def generic_process(gen_or_check: GenOrCheck) -> AsyncProcessGenerator:
200 for partition_4_value, partition_8_value, mask_list in [
201 (0, 0, [0xFFFF]),
202 (0, 1, [0xFF00, 0x00FF]),
203 (1, 0, [0xFFF0, 0x000F]),
204 (1, 1, [0xFF00, 0x00F0, 0x000F])]:
205 key_count = 8
206 if gen_or_check == GenOrCheck.Check:
207 with self.subTest(partition_4=partition_4_value,
208 partition_8=partition_8_value):
209 for key in range(key_count):
210 with self.subTest(key=key):
211 yield from self.subtest_key(input_count,
212 inputs,
213 module,
214 key,
215 mask_list,
216 gen_or_check)
217 else:
218 if gen_or_check == GenOrCheck.Generate:
219 yield partition_4.eq(partition_4_value)
220 yield partition_8.eq(partition_8_value)
221 for key in range(key_count):
222 yield from self.subtest_key(input_count,
223 inputs,
224 module,
225 key,
226 mask_list,
227 gen_or_check)
228
229 def generate_process() -> AsyncProcessGenerator:
230 yield from generic_process(GenOrCheck.Generate)
231
232 def check_process() -> AsyncProcessGenerator:
233 if delay_cycles != 0:
234 for _ in range(delay_cycles):
235 yield Tick()
236 yield from generic_process(GenOrCheck.Check)
237
238 sim.add_clock(2e-6)
239 sim.add_process(generate_process)
240 sim.add_process(check_process)
241 sim.run()
242
243 def subtest_file(self,
244 input_count: int,
245 register_levels: List[int]) -> None:
246 max_level = AddReduce.get_max_level(input_count)
247 for level in register_levels:
248 if level > max_level:
249 return
250 partition_4 = Signal()
251 partition_8 = Signal()
252 partition_points = PartitionPoints()
253 partition_points[4] = partition_4
254 partition_points[8] = partition_8
255 width = 16
256 inputs = [Signal(width, name=f"input_{i}")
257 for i in range(input_count)]
258 module = AddReduce(inputs,
259 width,
260 register_levels,
261 partition_points,
262 [])
263 file_name = "add_reduce"
264 if len(register_levels) != 0:
265 file_name += f"-{'_'.join(map(repr, register_levels))}"
266 file_name += f"-{input_count:02d}"
267 ports = [partition_4, partition_8, *inputs, module.o.output]
268 #create_ilang(module, ports, file_name)
269 with create_simulator(module, ports, file_name) as sim:
270 self.subtest_run_sim(input_count,
271 sim,
272 partition_4,
273 partition_8,
274 inputs,
275 module,
276 len(register_levels))
277
278 def subtest_register_levels(self, register_levels: List[int]) -> None:
279 for input_count in range(0, 16):
280 with self.subTest(input_count=input_count,
281 register_levels=repr(register_levels)):
282 self.subtest_file(input_count, register_levels)
283
284 def test_empty(self) -> None:
285 self.subtest_register_levels([])
286
287 def test_0(self) -> None:
288 self.subtest_register_levels([0])
289
290 def test_1(self) -> None:
291 self.subtest_register_levels([1])
292
293 def test_2(self) -> None:
294 self.subtest_register_levels([2])
295
296 def test_3(self) -> None:
297 self.subtest_register_levels([3])
298
299 def test_4(self) -> None:
300 self.subtest_register_levels([4])
301
302 def test_5(self) -> None:
303 self.subtest_register_levels([5])
304
305 def test_0(self) -> None:
306 self.subtest_register_levels([0])
307
308 def test_0_1(self) -> None:
309 self.subtest_register_levels([0, 1])
310
311 def test_0_1_2(self) -> None:
312 self.subtest_register_levels([0, 1, 2])
313
314 def test_0_1_2_3(self) -> None:
315 self.subtest_register_levels([0, 1, 2, 3])
316
317 def test_0_1_2_3_4(self) -> None:
318 self.subtest_register_levels([0, 1, 2, 3, 4])
319
320 def test_0_1_2_3_4_5(self) -> None:
321 self.subtest_register_levels([0, 1, 2, 3, 4, 5])
322
323 def test_0_2(self) -> None:
324 self.subtest_register_levels([0, 2])
325
326 def test_0_3(self) -> None:
327 self.subtest_register_levels([0, 3])
328
329 def test_0_4(self) -> None:
330 self.subtest_register_levels([0, 4])
331
332 def test_0_5(self) -> None:
333 self.subtest_register_levels([0, 5])
334
335
336 class SIMDMulLane:
337 def __init__(self,
338 a_signed: bool,
339 b_signed: bool,
340 bit_width: int,
341 high_half: bool):
342 self.a_signed = a_signed
343 self.b_signed = b_signed
344 self.bit_width = bit_width
345 self.high_half = high_half
346
347 def __repr__(self):
348 return f"SIMDMulLane({self.a_signed}, {self.b_signed}, " +\
349 f"{self.bit_width}, {self.high_half})"
350
351
352 class TestMul8_16_32_64(unittest.TestCase):
353 @staticmethod
354 def simd_mul(a: int, b: int, lanes: List[SIMDMulLane]) -> Tuple[int, int]:
355 output = 0
356 intermediate_output = 0
357 shift = 0
358 for lane in lanes:
359 a_signed = lane.a_signed or not lane.high_half
360 b_signed = lane.b_signed or not lane.high_half
361 mask = (1 << lane.bit_width) - 1
362 sign_bit = 1 << (lane.bit_width - 1)
363 a_part = (a >> shift) & mask
364 if a_signed and (a_part & sign_bit) != 0:
365 a_part -= 1 << lane.bit_width
366 b_part = (b >> shift) & mask
367 if b_signed and (b_part & sign_bit) != 0:
368 b_part -= 1 << lane.bit_width
369 value = a_part * b_part
370 value &= (1 << (lane.bit_width * 2)) - 1
371 intermediate_output |= value << (shift * 2)
372 if lane.high_half:
373 value >>= lane.bit_width
374 value &= mask
375 output |= value << shift
376 shift += lane.bit_width
377 return output, intermediate_output
378
379 @staticmethod
380 def get_test_cases(lanes: List[SIMDMulLane],
381 keys: Iterable[int]) -> Iterable[Tuple[int, int]]:
382 mask = (1 << 64) - 1
383 for i in range(8):
384 hash_input = f"{i} {lanes} {list(keys)}"
385 hash = sha256(hash_input.encode()).digest()
386 value = int.from_bytes(hash, byteorder="little")
387 yield (value & mask, value >> 64)
388 a = 0
389 b = 0
390 shift = 0
391 for lane in lanes:
392 a |= 1 << (shift + lane.bit_width - 1)
393 b |= 1 << (shift + lane.bit_width - 1)
394 shift += lane.bit_width
395 yield a, b
396
397 def test_simd_mul_lane(self):
398 self.assertEqual(f"{SIMDMulLane(True, True, 8, False)}",
399 "SIMDMulLane(True, True, 8, False)")
400
401 def test_simd_mul(self):
402 lanes = [SIMDMulLane(True,
403 True,
404 8,
405 True),
406 SIMDMulLane(False,
407 False,
408 8,
409 True),
410 SIMDMulLane(True,
411 True,
412 16,
413 False),
414 SIMDMulLane(True,
415 False,
416 32,
417 True)]
418 a = 0x0123456789ABCDEF
419 b = 0xFEDCBA9876543210
420 output = 0x0121FA00FE1C28FE
421 intermediate_output = 0x0121FA0023E20B28C94DFE1C280AFEF0
422 self.assertEqual(self.simd_mul(a, b, lanes),
423 (output, intermediate_output))
424 a = 0x8123456789ABCDEF
425 b = 0xFEDCBA9876543210
426 output = 0x81B39CB4FE1C28FE
427 intermediate_output = 0x81B39CB423E20B28C94DFE1C280AFEF0
428 self.assertEqual(self.simd_mul(a, b, lanes),
429 (output, intermediate_output))
430
431 def test_signed_mul_from_unsigned(self):
432 for i in range(0, 0x10):
433 for j in range(0, 0x10):
434 si = i if i & 8 else i - 0x10 # signed i
435 sj = j if j & 8 else j - 0x10 # signed j
436 mulu = i * j
437 mulsu = si * j
438 mul = si * sj
439 with self.subTest(i=i, j=j, si=si, sj=sj,
440 mulu=mulu, mulsu=mulsu, mul=mul):
441 mulsu2 = mulu
442 if si < 0:
443 mulsu2 += ~j << 4
444 mulsu2 += 1 << 4
445 self.assertEqual(mulsu & 0xFF, mulsu2 & 0xFF)
446 mul2 = mulsu2
447 if sj < 0:
448 mul2 += ~i << 4
449 mul2 += 1 << 4
450 self.assertEqual(mul & 0xFF, mul2 & 0xFF)
451
452 def subtest_value(self,
453 a: int,
454 b: int,
455 module: Mul8_16_32_64,
456 lanes: List[SIMDMulLane],
457 gen_or_check: GenOrCheck) -> AsyncProcessGenerator:
458 if gen_or_check == GenOrCheck.Generate:
459 yield module.a.eq(a)
460 yield module.b.eq(b)
461 output2, intermediate_output2 = self.simd_mul(a, b, lanes)
462 yield Delay(1e-6)
463 if gen_or_check == GenOrCheck.Check:
464 intermediate_output = (yield module.intermediate_output)
465 self.assertEqual(intermediate_output,
466 intermediate_output2,
467 f"0x{intermediate_output:X} "
468 + f"!= 0x{intermediate_output2:X}")
469 output = (yield module.output)
470 self.assertEqual(output, output2, f"0x{output:X} != 0x{output2:X}")
471 yield Tick()
472
473 def subtest_lanes_2(self,
474 lanes: List[SIMDMulLane],
475 module: Mul8_16_32_64,
476 gen_or_check: GenOrCheck) -> AsyncProcessGenerator:
477 bit_index = 8
478 part_index = 0
479 for lane in lanes:
480 if lane.high_half:
481 if lane.a_signed:
482 if lane.b_signed:
483 op = OP_MUL_SIGNED_HIGH
484 else:
485 op = OP_MUL_SIGNED_UNSIGNED_HIGH
486 else:
487 self.assertFalse(lane.b_signed,
488 "unsigned * signed not supported")
489 op = OP_MUL_UNSIGNED_HIGH
490 else:
491 op = OP_MUL_LOW
492 self.assertEqual(lane.bit_width % 8, 0)
493 for i in range(lane.bit_width // 8):
494 if gen_or_check == GenOrCheck.Generate:
495 yield module.part_ops[part_index].eq(op)
496 part_index += 1
497 for i in range(lane.bit_width // 8 - 1):
498 if gen_or_check == GenOrCheck.Generate:
499 yield module.part_pts[bit_index].eq(0)
500 bit_index += 8
501 if bit_index < 64 and gen_or_check == GenOrCheck.Generate:
502 yield module.part_pts[bit_index].eq(1)
503 bit_index += 8
504 self.assertEqual(part_index, 8)
505 for a, b in self.get_test_cases(lanes, ()):
506 if gen_or_check == GenOrCheck.Check:
507 with self.subTest(a=f"{a:X}", b=f"{b:X}"):
508 yield from self.subtest_value(a, b, module, lanes, gen_or_check)
509 else:
510 yield from self.subtest_value(a, b, module, lanes, gen_or_check)
511
512 def subtest_lanes(self,
513 lanes: List[SIMDMulLane],
514 module: Mul8_16_32_64,
515 gen_or_check: GenOrCheck) -> AsyncProcessGenerator:
516 if gen_or_check == GenOrCheck.Check:
517 with self.subTest(lanes=repr(lanes)):
518 yield from self.subtest_lanes_2(lanes, module, gen_or_check)
519 else:
520 yield from self.subtest_lanes_2(lanes, module, gen_or_check)
521
522 def subtest_file(self,
523 register_levels: List[int]) -> None:
524 module = Mul8_16_32_64(register_levels)
525 file_name = "mul8_16_32_64"
526 if len(register_levels) != 0:
527 file_name += f"-{'_'.join(map(repr, register_levels))}"
528 ports = [module.a,
529 module.b,
530 module.intermediate_output,
531 module.output]
532 ports.extend(module.part_ops)
533 ports.extend(module.part_pts.values())
534 with create_simulator(module, ports, file_name) as sim:
535 def process(gen_or_check: GenOrCheck) -> AsyncProcessGenerator:
536 for a_signed in False, True:
537 for b_signed in False, True:
538 if not a_signed and b_signed:
539 continue
540 for high_half in False, True:
541 if not high_half and not (a_signed and b_signed):
542 continue
543 yield from self.subtest_lanes(
544 [SIMDMulLane(a_signed,
545 b_signed,
546 64,
547 high_half)],
548 module,
549 gen_or_check)
550 yield from self.subtest_lanes(
551 [SIMDMulLane(a_signed,
552 b_signed,
553 32,
554 high_half)] * 2,
555 module,
556 gen_or_check)
557 yield from self.subtest_lanes(
558 [SIMDMulLane(a_signed,
559 b_signed,
560 16,
561 high_half)] * 4,
562 module,
563 gen_or_check)
564 yield from self.subtest_lanes(
565 [SIMDMulLane(a_signed,
566 b_signed,
567 8,
568 high_half)] * 8,
569 module,
570 gen_or_check)
571 yield from self.subtest_lanes([SIMDMulLane(False,
572 False,
573 32,
574 True),
575 SIMDMulLane(False,
576 False,
577 16,
578 True),
579 SIMDMulLane(False,
580 False,
581 8,
582 True),
583 SIMDMulLane(False,
584 False,
585 8,
586 True)],
587 module,
588 gen_or_check)
589 yield from self.subtest_lanes([SIMDMulLane(True,
590 False,
591 32,
592 True),
593 SIMDMulLane(True,
594 True,
595 16,
596 False),
597 SIMDMulLane(True,
598 True,
599 8,
600 True),
601 SIMDMulLane(False,
602 False,
603 8,
604 True)],
605 module,
606 gen_or_check)
607 yield from self.subtest_lanes([SIMDMulLane(True,
608 True,
609 8,
610 True),
611 SIMDMulLane(False,
612 False,
613 8,
614 True),
615 SIMDMulLane(True,
616 True,
617 16,
618 False),
619 SIMDMulLane(True,
620 False,
621 32,
622 True)],
623 module,
624 gen_or_check)
625
626 def generate_process() -> AsyncProcessGenerator:
627 yield from process(GenOrCheck.Generate)
628
629 def check_process() -> AsyncProcessGenerator:
630 if len(register_levels) != 0:
631 for _ in register_levels:
632 yield Tick()
633 yield from process(GenOrCheck.Check)
634
635 sim.add_clock(2e-6)
636 sim.add_process(generate_process)
637 sim.add_process(check_process)
638 sim.run()
639
640 def subtest_register_levels(self, register_levels: List[int]) -> None:
641 with self.subTest(register_levels=repr(register_levels)):
642 self.subtest_file(register_levels)
643
644 def test_empty(self) -> None:
645 self.subtest_register_levels([])
646
647 def test_0(self) -> None:
648 self.subtest_register_levels([0])
649
650 def test_1(self) -> None:
651 self.subtest_register_levels([1])
652
653 def test_2(self) -> None:
654 self.subtest_register_levels([2])
655
656 def test_3(self) -> None:
657 self.subtest_register_levels([3])
658
659 def test_4(self) -> None:
660 self.subtest_register_levels([4])
661
662 def test_5(self) -> None:
663 self.subtest_register_levels([5])
664
665 def test_6(self) -> None:
666 self.subtest_register_levels([6])
667
668 def test_7(self) -> None:
669 self.subtest_register_levels([7])
670
671 def test_8(self) -> None:
672 self.subtest_register_levels([8])
673
674 def test_9(self) -> None:
675 self.subtest_register_levels([9])
676
677 def test_10(self) -> None:
678 self.subtest_register_levels([10])
679
680 def test_0(self) -> None:
681 self.subtest_register_levels([0])
682
683 def test_0_1(self) -> None:
684 self.subtest_register_levels([0, 1])
685
686 def test_0_1_2(self) -> None:
687 self.subtest_register_levels([0, 1, 2])
688
689 def test_0_1_2_3(self) -> None:
690 self.subtest_register_levels([0, 1, 2, 3])
691
692 def test_0_1_2_3_4(self) -> None:
693 self.subtest_register_levels([0, 1, 2, 3, 4])
694
695 def test_0_1_2_3_4_5(self) -> None:
696 self.subtest_register_levels([0, 1, 2, 3, 4, 5])
697
698 def test_0_1_2_3_4_5_6(self) -> None:
699 self.subtest_register_levels([0, 1, 2, 3, 4, 5, 6])
700
701 def test_0_1_2_3_4_5_6_7(self) -> None:
702 self.subtest_register_levels([0, 1, 2, 3, 4, 5, 6, 7])
703
704 def test_0_1_2_3_4_5_6_7_8(self) -> None:
705 self.subtest_register_levels([0, 1, 2, 3, 4, 5, 6, 7, 8])
706
707 def test_0_1_2_3_4_5_6_7_8_9(self) -> None:
708 self.subtest_register_levels([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
709
710 def test_0_1_2_3_4_5_6_7_8_9_10(self) -> None:
711 self.subtest_register_levels([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
712
713 def test_0_2(self) -> None:
714 self.subtest_register_levels([0, 2])
715
716 def test_0_3(self) -> None:
717 self.subtest_register_levels([0, 3])
718
719 def test_0_4(self) -> None:
720 self.subtest_register_levels([0, 4])
721
722 def test_0_5(self) -> None:
723 self.subtest_register_levels([0, 5])
724
725 def test_0_6(self) -> None:
726 self.subtest_register_levels([0, 6])
727
728 def test_0_7(self) -> None:
729 self.subtest_register_levels([0, 7])
730
731 def test_0_8(self) -> None:
732 self.subtest_register_levels([0, 8])
733
734 def test_0_9(self) -> None:
735 self.subtest_register_levels([0, 9])
736
737 def test_0_10(self) -> None:
738 self.subtest_register_levels([0, 10])
739
740 if __name__ == '__main__':
741 unittest.main()