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