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