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