e1120fd708126be391789c2779890c5f902fc32a
[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(0.1e-6)
60 self.assertEqual((yield mask), 0xFFFD)
61 yield partition_point_10.eq(1)
62 yield Delay(0.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(0.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(0.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, if_exists=True)
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, a_signed, b_signed, bit_width, high_half):
338 self.a_signed = a_signed
339 self.b_signed = b_signed
340 self.bit_width = bit_width
341 self.high_half = high_half
342
343 def __repr__(self):
344 return f"SIMDMulLane({self.a_signed}, {self.b_signed}, " +\
345 f"{self.bit_width}, {self.high_half})"
346
347
348 def simd_mul(a, b, lanes):
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 class TestMul8_16_32_64(unittest.TestCase):
374
375 @staticmethod
376 def get_tst_cases(lanes: List[SIMDMulLane],
377 keys: Iterable[int]) -> Iterable[Tuple[int, int]]:
378 mask = (1 << 64) - 1
379 for i in range(8):
380 hash_input = f"{i} {lanes} {list(keys)}"
381 hash = sha256(hash_input.encode()).digest()
382 value = int.from_bytes(hash, byteorder="little")
383 yield (value & mask, value >> 64)
384 a = 0
385 b = 0
386 shift = 0
387 for lane in lanes:
388 a |= 1 << (shift + lane.bit_width - 1)
389 b |= 1 << (shift + lane.bit_width - 1)
390 shift += lane.bit_width
391 yield a, b
392
393 def test_simd_mul_lane(self):
394 self.assertEqual(f"{SIMDMulLane(True, True, 8, False)}",
395 "SIMDMulLane(True, True, 8, False)")
396
397 def test_simd_mul(self):
398 lanes = [SIMDMulLane(True,
399 True,
400 8,
401 True),
402 SIMDMulLane(False,
403 False,
404 8,
405 True),
406 SIMDMulLane(True,
407 True,
408 16,
409 False),
410 SIMDMulLane(True,
411 False,
412 32,
413 True)]
414 a = 0x0123456789ABCDEF
415 b = 0xFEDCBA9876543210
416 output = 0x0121FA00FE1C28FE
417 intermediate_output = 0x0121FA0023E20B28C94DFE1C280AFEF0
418 self.assertEqual(simd_mul(a, b, lanes),
419 (output, intermediate_output))
420 a = 0x8123456789ABCDEF
421 b = 0xFEDCBA9876543210
422 output = 0x81B39CB4FE1C28FE
423 intermediate_output = 0x81B39CB423E20B28C94DFE1C280AFEF0
424 self.assertEqual(simd_mul(a, b, lanes),
425 (output, intermediate_output))
426
427 def test_signed_mul_from_unsigned(self):
428 for i in range(0, 0x10):
429 for j in range(0, 0x10):
430 si = i if i & 8 else i - 0x10 # signed i
431 sj = j if j & 8 else j - 0x10 # signed j
432 mulu = i * j
433 mulsu = si * j
434 mul = si * sj
435 with self.subTest(i=i, j=j, si=si, sj=sj,
436 mulu=mulu, mulsu=mulsu, mul=mul):
437 mulsu2 = mulu
438 if si < 0:
439 mulsu2 += ~j << 4
440 mulsu2 += 1 << 4
441 self.assertEqual(mulsu & 0xFF, mulsu2 & 0xFF)
442 mul2 = mulsu2
443 if sj < 0:
444 mul2 += ~i << 4
445 mul2 += 1 << 4
446 self.assertEqual(mul & 0xFF, mul2 & 0xFF)
447
448 def subtest_value(self,
449 a: int,
450 b: int,
451 module: Mul8_16_32_64,
452 lanes: List[SIMDMulLane],
453 gen_or_check: GenOrCheck) -> AsyncProcessGenerator:
454 if gen_or_check == GenOrCheck.Generate:
455 yield module.a.eq(a)
456 yield module.b.eq(b)
457 output2, intermediate_output2 = simd_mul(a, b, lanes)
458 yield Delay(0.1e-6)
459 if gen_or_check == GenOrCheck.Check:
460 intermediate_output = (yield module.intermediate_output)
461 self.assertEqual(intermediate_output,
462 intermediate_output2,
463 f"0x{intermediate_output:X} "
464 + f"!= 0x{intermediate_output2:X}")
465 output = (yield module.output)
466 self.assertEqual(output, output2, f"0x{output:X} != 0x{output2:X}")
467 yield Tick()
468
469 def subtest_lanes_2(self,
470 lanes: List[SIMDMulLane],
471 module: Mul8_16_32_64,
472 gen_or_check: GenOrCheck) -> AsyncProcessGenerator:
473 bit_index = 8
474 part_index = 0
475 for lane in lanes:
476 if lane.high_half:
477 if lane.a_signed:
478 if lane.b_signed:
479 op = OP_MUL_SIGNED_HIGH
480 else:
481 op = OP_MUL_SIGNED_UNSIGNED_HIGH
482 else:
483 self.assertFalse(lane.b_signed,
484 "unsigned * signed not supported")
485 op = OP_MUL_UNSIGNED_HIGH
486 else:
487 op = OP_MUL_LOW
488 self.assertEqual(lane.bit_width % 8, 0)
489 for i in range(lane.bit_width // 8):
490 if gen_or_check == GenOrCheck.Generate:
491 yield module.part_ops[part_index].eq(op)
492 part_index += 1
493 for i in range(lane.bit_width // 8 - 1):
494 if gen_or_check == GenOrCheck.Generate:
495 yield module.part_pts[bit_index].eq(0)
496 bit_index += 8
497 if bit_index < 64 and gen_or_check == GenOrCheck.Generate:
498 yield module.part_pts[bit_index].eq(1)
499 bit_index += 8
500 self.assertEqual(part_index, 8)
501 for a, b in self.get_tst_cases(lanes, ()):
502 if gen_or_check == GenOrCheck.Check:
503 with self.subTest(a=f"{a:X}", b=f"{b:X}"):
504 yield from self.subtest_value(a, b, module, lanes, gen_or_check)
505 else:
506 yield from self.subtest_value(a, b, module, lanes, gen_or_check)
507
508 def subtest_lanes(self,
509 lanes: List[SIMDMulLane],
510 module: Mul8_16_32_64,
511 gen_or_check: GenOrCheck) -> AsyncProcessGenerator:
512 if gen_or_check == GenOrCheck.Check:
513 with self.subTest(lanes=repr(lanes)):
514 yield from self.subtest_lanes_2(lanes, module, gen_or_check)
515 else:
516 yield from self.subtest_lanes_2(lanes, module, gen_or_check)
517
518 def subtest_file(self,
519 register_levels: List[int]) -> None:
520 module = Mul8_16_32_64(register_levels)
521 file_name = "mul8_16_32_64"
522 if len(register_levels) != 0:
523 file_name += f"-{'_'.join(map(repr, register_levels))}"
524 ports = [module.a,
525 module.b,
526 module.intermediate_output,
527 module.output]
528 ports.extend(module.part_ops)
529 ports.extend(module.part_pts.values())
530 with create_simulator(module, ports, file_name) as sim:
531 def process(gen_or_check: GenOrCheck) -> AsyncProcessGenerator:
532 for a_signed in False, True:
533 for b_signed in False, True:
534 if not a_signed and b_signed:
535 continue
536 for high_half in False, True:
537 if not high_half and not (a_signed and b_signed):
538 continue
539 yield from self.subtest_lanes(
540 [SIMDMulLane(a_signed,
541 b_signed,
542 64,
543 high_half)],
544 module,
545 gen_or_check)
546 yield from self.subtest_lanes(
547 [SIMDMulLane(a_signed,
548 b_signed,
549 32,
550 high_half)] * 2,
551 module,
552 gen_or_check)
553 yield from self.subtest_lanes(
554 [SIMDMulLane(a_signed,
555 b_signed,
556 16,
557 high_half)] * 4,
558 module,
559 gen_or_check)
560 yield from self.subtest_lanes(
561 [SIMDMulLane(a_signed,
562 b_signed,
563 8,
564 high_half)] * 8,
565 module,
566 gen_or_check)
567 yield from self.subtest_lanes([SIMDMulLane(False,
568 False,
569 32,
570 True),
571 SIMDMulLane(False,
572 False,
573 16,
574 True),
575 SIMDMulLane(False,
576 False,
577 8,
578 True),
579 SIMDMulLane(False,
580 False,
581 8,
582 True)],
583 module,
584 gen_or_check)
585 yield from self.subtest_lanes([SIMDMulLane(True,
586 False,
587 32,
588 True),
589 SIMDMulLane(True,
590 True,
591 16,
592 False),
593 SIMDMulLane(True,
594 True,
595 8,
596 True),
597 SIMDMulLane(False,
598 False,
599 8,
600 True)],
601 module,
602 gen_or_check)
603 yield from self.subtest_lanes([SIMDMulLane(True,
604 True,
605 8,
606 True),
607 SIMDMulLane(False,
608 False,
609 8,
610 True),
611 SIMDMulLane(True,
612 True,
613 16,
614 False),
615 SIMDMulLane(True,
616 False,
617 32,
618 True)],
619 module,
620 gen_or_check)
621
622 def generate_process() -> AsyncProcessGenerator:
623 yield from process(GenOrCheck.Generate)
624
625 def check_process() -> AsyncProcessGenerator:
626 if len(register_levels) != 0:
627 for _ in register_levels:
628 yield Tick()
629 yield from process(GenOrCheck.Check)
630
631 sim.add_clock(2e-6, if_exists=True)
632 sim.add_process(generate_process)
633 sim.add_process(check_process)
634 sim.run()
635
636 def subtest_register_levels(self, register_levels: List[int]) -> None:
637 with self.subTest(register_levels=repr(register_levels)):
638 self.subtest_file(register_levels)
639
640 def test_empty(self) -> None:
641 self.subtest_register_levels([])
642
643 def test_0(self) -> None:
644 self.subtest_register_levels([0])
645
646 def test_1(self) -> None:
647 self.subtest_register_levels([1])
648
649 def test_2(self) -> None:
650 self.subtest_register_levels([2])
651
652 def test_3(self) -> None:
653 self.subtest_register_levels([3])
654
655 def test_4(self) -> None:
656 self.subtest_register_levels([4])
657
658 def test_5(self) -> None:
659 self.subtest_register_levels([5])
660
661 def test_6(self) -> None:
662 self.subtest_register_levels([6])
663
664 def test_7(self) -> None:
665 self.subtest_register_levels([7])
666
667 def test_8(self) -> None:
668 self.subtest_register_levels([8])
669
670 def test_9(self) -> None:
671 self.subtest_register_levels([9])
672
673 def test_10(self) -> None:
674 self.subtest_register_levels([10])
675
676 def test_0(self) -> None:
677 self.subtest_register_levels([0])
678
679 def test_0_1(self) -> None:
680 self.subtest_register_levels([0, 1])
681
682 def test_0_1_2(self) -> None:
683 self.subtest_register_levels([0, 1, 2])
684
685 def test_0_1_2_3(self) -> None:
686 self.subtest_register_levels([0, 1, 2, 3])
687
688 def test_0_1_2_3_4(self) -> None:
689 self.subtest_register_levels([0, 1, 2, 3, 4])
690
691 def test_0_1_2_3_4_5(self) -> None:
692 self.subtest_register_levels([0, 1, 2, 3, 4, 5])
693
694 def test_0_1_2_3_4_5_6(self) -> None:
695 self.subtest_register_levels([0, 1, 2, 3, 4, 5, 6])
696
697 def test_0_1_2_3_4_5_6_7(self) -> None:
698 self.subtest_register_levels([0, 1, 2, 3, 4, 5, 6, 7])
699
700 def test_0_1_2_3_4_5_6_7_8(self) -> None:
701 self.subtest_register_levels([0, 1, 2, 3, 4, 5, 6, 7, 8])
702
703 def test_0_1_2_3_4_5_6_7_8_9(self) -> None:
704 self.subtest_register_levels([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
705
706 def test_0_1_2_3_4_5_6_7_8_9_10(self) -> None:
707 self.subtest_register_levels([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
708
709 def test_0_2(self) -> None:
710 self.subtest_register_levels([0, 2])
711
712 def test_0_3(self) -> None:
713 self.subtest_register_levels([0, 3])
714
715 def test_0_4(self) -> None:
716 self.subtest_register_levels([0, 4])
717
718 def test_0_5(self) -> None:
719 self.subtest_register_levels([0, 5])
720
721 def test_0_6(self) -> None:
722 self.subtest_register_levels([0, 6])
723
724 def test_0_7(self) -> None:
725 self.subtest_register_levels([0, 7])
726
727 def test_0_8(self) -> None:
728 self.subtest_register_levels([0, 8])
729
730 def test_0_9(self) -> None:
731 self.subtest_register_levels([0, 9])
732
733 def test_0_10(self) -> None:
734 self.subtest_register_levels([0, 10])
735
736 if __name__ == '__main__':
737 unittest.main()