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