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