skip add clock on combinatorial tests
[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 if "sync" in sim._domains:
239 sim.add_clock(2e-6)
240 sim.add_process(generate_process)
241 sim.add_process(check_process)
242 sim.run()
243
244 def subtest_file(self,
245 input_count: int,
246 register_levels: List[int]) -> None:
247 max_level = AddReduce.get_max_level(input_count)
248 for level in register_levels:
249 if level > max_level:
250 return
251 partition_4 = Signal()
252 partition_8 = Signal()
253 partition_points = PartitionPoints()
254 partition_points[4] = partition_4
255 partition_points[8] = partition_8
256 width = 16
257 inputs = [Signal(width, name=f"input_{i}")
258 for i in range(input_count)]
259 module = AddReduce(inputs,
260 width,
261 register_levels,
262 partition_points,
263 [])
264 file_name = "add_reduce"
265 if len(register_levels) != 0:
266 file_name += f"-{'_'.join(map(repr, register_levels))}"
267 file_name += f"-{input_count:02d}"
268 ports = [partition_4, partition_8, *inputs, module.o.output]
269 #create_ilang(module, ports, file_name)
270 with create_simulator(module, ports, file_name) as sim:
271 self.subtest_run_sim(input_count,
272 sim,
273 partition_4,
274 partition_8,
275 inputs,
276 module,
277 len(register_levels))
278
279 def subtest_register_levels(self, register_levels: List[int]) -> None:
280 for input_count in range(0, 16):
281 with self.subTest(input_count=input_count,
282 register_levels=repr(register_levels)):
283 self.subtest_file(input_count, register_levels)
284
285 def test_empty(self) -> None:
286 self.subtest_register_levels([])
287
288 def test_0(self) -> None:
289 self.subtest_register_levels([0])
290
291 def test_1(self) -> None:
292 self.subtest_register_levels([1])
293
294 def test_2(self) -> None:
295 self.subtest_register_levels([2])
296
297 def test_3(self) -> None:
298 self.subtest_register_levels([3])
299
300 def test_4(self) -> None:
301 self.subtest_register_levels([4])
302
303 def test_5(self) -> None:
304 self.subtest_register_levels([5])
305
306 def test_0(self) -> None:
307 self.subtest_register_levels([0])
308
309 def test_0_1(self) -> None:
310 self.subtest_register_levels([0, 1])
311
312 def test_0_1_2(self) -> None:
313 self.subtest_register_levels([0, 1, 2])
314
315 def test_0_1_2_3(self) -> None:
316 self.subtest_register_levels([0, 1, 2, 3])
317
318 def test_0_1_2_3_4(self) -> None:
319 self.subtest_register_levels([0, 1, 2, 3, 4])
320
321 def test_0_1_2_3_4_5(self) -> None:
322 self.subtest_register_levels([0, 1, 2, 3, 4, 5])
323
324 def test_0_2(self) -> None:
325 self.subtest_register_levels([0, 2])
326
327 def test_0_3(self) -> None:
328 self.subtest_register_levels([0, 3])
329
330 def test_0_4(self) -> None:
331 self.subtest_register_levels([0, 4])
332
333 def test_0_5(self) -> None:
334 self.subtest_register_levels([0, 5])
335
336
337 class SIMDMulLane:
338 def __init__(self,
339 a_signed: bool,
340 b_signed: bool,
341 bit_width: int,
342 high_half: bool):
343 self.a_signed = a_signed
344 self.b_signed = b_signed
345 self.bit_width = bit_width
346 self.high_half = high_half
347
348 def __repr__(self):
349 return f"SIMDMulLane({self.a_signed}, {self.b_signed}, " +\
350 f"{self.bit_width}, {self.high_half})"
351
352
353 class TestMul8_16_32_64(unittest.TestCase):
354 @staticmethod
355 def simd_mul(a: int, b: int, lanes: List[SIMDMulLane]) -> Tuple[int, int]:
356 output = 0
357 intermediate_output = 0
358 shift = 0
359 for lane in lanes:
360 a_signed = lane.a_signed or not lane.high_half
361 b_signed = lane.b_signed or not lane.high_half
362 mask = (1 << lane.bit_width) - 1
363 sign_bit = 1 << (lane.bit_width - 1)
364 a_part = (a >> shift) & mask
365 if a_signed and (a_part & sign_bit) != 0:
366 a_part -= 1 << lane.bit_width
367 b_part = (b >> shift) & mask
368 if b_signed and (b_part & sign_bit) != 0:
369 b_part -= 1 << lane.bit_width
370 value = a_part * b_part
371 value &= (1 << (lane.bit_width * 2)) - 1
372 intermediate_output |= value << (shift * 2)
373 if lane.high_half:
374 value >>= lane.bit_width
375 value &= mask
376 output |= value << shift
377 shift += lane.bit_width
378 return output, intermediate_output
379
380 @staticmethod
381 def get_test_cases(lanes: List[SIMDMulLane],
382 keys: Iterable[int]) -> Iterable[Tuple[int, int]]:
383 mask = (1 << 64) - 1
384 for i in range(8):
385 hash_input = f"{i} {lanes} {list(keys)}"
386 hash = sha256(hash_input.encode()).digest()
387 value = int.from_bytes(hash, byteorder="little")
388 yield (value & mask, value >> 64)
389 a = 0
390 b = 0
391 shift = 0
392 for lane in lanes:
393 a |= 1 << (shift + lane.bit_width - 1)
394 b |= 1 << (shift + lane.bit_width - 1)
395 shift += lane.bit_width
396 yield a, b
397
398 def test_simd_mul_lane(self):
399 self.assertEqual(f"{SIMDMulLane(True, True, 8, False)}",
400 "SIMDMulLane(True, True, 8, False)")
401
402 def test_simd_mul(self):
403 lanes = [SIMDMulLane(True,
404 True,
405 8,
406 True),
407 SIMDMulLane(False,
408 False,
409 8,
410 True),
411 SIMDMulLane(True,
412 True,
413 16,
414 False),
415 SIMDMulLane(True,
416 False,
417 32,
418 True)]
419 a = 0x0123456789ABCDEF
420 b = 0xFEDCBA9876543210
421 output = 0x0121FA00FE1C28FE
422 intermediate_output = 0x0121FA0023E20B28C94DFE1C280AFEF0
423 self.assertEqual(self.simd_mul(a, b, lanes),
424 (output, intermediate_output))
425 a = 0x8123456789ABCDEF
426 b = 0xFEDCBA9876543210
427 output = 0x81B39CB4FE1C28FE
428 intermediate_output = 0x81B39CB423E20B28C94DFE1C280AFEF0
429 self.assertEqual(self.simd_mul(a, b, lanes),
430 (output, intermediate_output))
431
432 def test_signed_mul_from_unsigned(self):
433 for i in range(0, 0x10):
434 for j in range(0, 0x10):
435 si = i if i & 8 else i - 0x10 # signed i
436 sj = j if j & 8 else j - 0x10 # signed j
437 mulu = i * j
438 mulsu = si * j
439 mul = si * sj
440 with self.subTest(i=i, j=j, si=si, sj=sj,
441 mulu=mulu, mulsu=mulsu, mul=mul):
442 mulsu2 = mulu
443 if si < 0:
444 mulsu2 += ~j << 4
445 mulsu2 += 1 << 4
446 self.assertEqual(mulsu & 0xFF, mulsu2 & 0xFF)
447 mul2 = mulsu2
448 if sj < 0:
449 mul2 += ~i << 4
450 mul2 += 1 << 4
451 self.assertEqual(mul & 0xFF, mul2 & 0xFF)
452
453 def subtest_value(self,
454 a: int,
455 b: int,
456 module: Mul8_16_32_64,
457 lanes: List[SIMDMulLane],
458 gen_or_check: GenOrCheck) -> AsyncProcessGenerator:
459 if gen_or_check == GenOrCheck.Generate:
460 yield module.a.eq(a)
461 yield module.b.eq(b)
462 output2, intermediate_output2 = self.simd_mul(a, b, lanes)
463 yield Delay(0.1e-6)
464 if gen_or_check == GenOrCheck.Check:
465 intermediate_output = (yield module.intermediate_output)
466 self.assertEqual(intermediate_output,
467 intermediate_output2,
468 f"0x{intermediate_output:X} "
469 + f"!= 0x{intermediate_output2:X}")
470 output = (yield module.output)
471 self.assertEqual(output, output2, f"0x{output:X} != 0x{output2:X}")
472 yield Tick()
473
474 def subtest_lanes_2(self,
475 lanes: List[SIMDMulLane],
476 module: Mul8_16_32_64,
477 gen_or_check: GenOrCheck) -> AsyncProcessGenerator:
478 bit_index = 8
479 part_index = 0
480 for lane in lanes:
481 if lane.high_half:
482 if lane.a_signed:
483 if lane.b_signed:
484 op = OP_MUL_SIGNED_HIGH
485 else:
486 op = OP_MUL_SIGNED_UNSIGNED_HIGH
487 else:
488 self.assertFalse(lane.b_signed,
489 "unsigned * signed not supported")
490 op = OP_MUL_UNSIGNED_HIGH
491 else:
492 op = OP_MUL_LOW
493 self.assertEqual(lane.bit_width % 8, 0)
494 for i in range(lane.bit_width // 8):
495 if gen_or_check == GenOrCheck.Generate:
496 yield module.part_ops[part_index].eq(op)
497 part_index += 1
498 for i in range(lane.bit_width // 8 - 1):
499 if gen_or_check == GenOrCheck.Generate:
500 yield module.part_pts[bit_index].eq(0)
501 bit_index += 8
502 if bit_index < 64 and gen_or_check == GenOrCheck.Generate:
503 yield module.part_pts[bit_index].eq(1)
504 bit_index += 8
505 self.assertEqual(part_index, 8)
506 for a, b in self.get_test_cases(lanes, ()):
507 if gen_or_check == GenOrCheck.Check:
508 with self.subTest(a=f"{a:X}", b=f"{b:X}"):
509 yield from self.subtest_value(a, b, module, lanes, gen_or_check)
510 else:
511 yield from self.subtest_value(a, b, module, lanes, gen_or_check)
512
513 def subtest_lanes(self,
514 lanes: List[SIMDMulLane],
515 module: Mul8_16_32_64,
516 gen_or_check: GenOrCheck) -> AsyncProcessGenerator:
517 if gen_or_check == GenOrCheck.Check:
518 with self.subTest(lanes=repr(lanes)):
519 yield from self.subtest_lanes_2(lanes, module, gen_or_check)
520 else:
521 yield from self.subtest_lanes_2(lanes, module, gen_or_check)
522
523 def subtest_file(self,
524 register_levels: List[int]) -> None:
525 module = Mul8_16_32_64(register_levels)
526 file_name = "mul8_16_32_64"
527 if len(register_levels) != 0:
528 file_name += f"-{'_'.join(map(repr, register_levels))}"
529 ports = [module.a,
530 module.b,
531 module.intermediate_output,
532 module.output]
533 ports.extend(module.part_ops)
534 ports.extend(module.part_pts.values())
535 with create_simulator(module, ports, file_name) as sim:
536 def process(gen_or_check: GenOrCheck) -> AsyncProcessGenerator:
537 for a_signed in False, True:
538 for b_signed in False, True:
539 if not a_signed and b_signed:
540 continue
541 for high_half in False, True:
542 if not high_half and not (a_signed and b_signed):
543 continue
544 yield from self.subtest_lanes(
545 [SIMDMulLane(a_signed,
546 b_signed,
547 64,
548 high_half)],
549 module,
550 gen_or_check)
551 yield from self.subtest_lanes(
552 [SIMDMulLane(a_signed,
553 b_signed,
554 32,
555 high_half)] * 2,
556 module,
557 gen_or_check)
558 yield from self.subtest_lanes(
559 [SIMDMulLane(a_signed,
560 b_signed,
561 16,
562 high_half)] * 4,
563 module,
564 gen_or_check)
565 yield from self.subtest_lanes(
566 [SIMDMulLane(a_signed,
567 b_signed,
568 8,
569 high_half)] * 8,
570 module,
571 gen_or_check)
572 yield from self.subtest_lanes([SIMDMulLane(False,
573 False,
574 32,
575 True),
576 SIMDMulLane(False,
577 False,
578 16,
579 True),
580 SIMDMulLane(False,
581 False,
582 8,
583 True),
584 SIMDMulLane(False,
585 False,
586 8,
587 True)],
588 module,
589 gen_or_check)
590 yield from self.subtest_lanes([SIMDMulLane(True,
591 False,
592 32,
593 True),
594 SIMDMulLane(True,
595 True,
596 16,
597 False),
598 SIMDMulLane(True,
599 True,
600 8,
601 True),
602 SIMDMulLane(False,
603 False,
604 8,
605 True)],
606 module,
607 gen_or_check)
608 yield from self.subtest_lanes([SIMDMulLane(True,
609 True,
610 8,
611 True),
612 SIMDMulLane(False,
613 False,
614 8,
615 True),
616 SIMDMulLane(True,
617 True,
618 16,
619 False),
620 SIMDMulLane(True,
621 False,
622 32,
623 True)],
624 module,
625 gen_or_check)
626
627 def generate_process() -> AsyncProcessGenerator:
628 yield from process(GenOrCheck.Generate)
629
630 def check_process() -> AsyncProcessGenerator:
631 if len(register_levels) != 0:
632 for _ in register_levels:
633 yield Tick()
634 yield from process(GenOrCheck.Check)
635
636 if "sync" in sim._domains:
637 sim.add_clock(2e-6)
638 sim.add_process(generate_process)
639 sim.add_process(check_process)
640 sim.run()
641
642 def subtest_register_levels(self, register_levels: List[int]) -> None:
643 with self.subTest(register_levels=repr(register_levels)):
644 self.subtest_file(register_levels)
645
646 def test_empty(self) -> None:
647 self.subtest_register_levels([])
648
649 def test_0(self) -> None:
650 self.subtest_register_levels([0])
651
652 def test_1(self) -> None:
653 self.subtest_register_levels([1])
654
655 def test_2(self) -> None:
656 self.subtest_register_levels([2])
657
658 def test_3(self) -> None:
659 self.subtest_register_levels([3])
660
661 def test_4(self) -> None:
662 self.subtest_register_levels([4])
663
664 def test_5(self) -> None:
665 self.subtest_register_levels([5])
666
667 def test_6(self) -> None:
668 self.subtest_register_levels([6])
669
670 def test_7(self) -> None:
671 self.subtest_register_levels([7])
672
673 def test_8(self) -> None:
674 self.subtest_register_levels([8])
675
676 def test_9(self) -> None:
677 self.subtest_register_levels([9])
678
679 def test_10(self) -> None:
680 self.subtest_register_levels([10])
681
682 def test_0(self) -> None:
683 self.subtest_register_levels([0])
684
685 def test_0_1(self) -> None:
686 self.subtest_register_levels([0, 1])
687
688 def test_0_1_2(self) -> None:
689 self.subtest_register_levels([0, 1, 2])
690
691 def test_0_1_2_3(self) -> None:
692 self.subtest_register_levels([0, 1, 2, 3])
693
694 def test_0_1_2_3_4(self) -> None:
695 self.subtest_register_levels([0, 1, 2, 3, 4])
696
697 def test_0_1_2_3_4_5(self) -> None:
698 self.subtest_register_levels([0, 1, 2, 3, 4, 5])
699
700 def test_0_1_2_3_4_5_6(self) -> None:
701 self.subtest_register_levels([0, 1, 2, 3, 4, 5, 6])
702
703 def test_0_1_2_3_4_5_6_7(self) -> None:
704 self.subtest_register_levels([0, 1, 2, 3, 4, 5, 6, 7])
705
706 def test_0_1_2_3_4_5_6_7_8(self) -> None:
707 self.subtest_register_levels([0, 1, 2, 3, 4, 5, 6, 7, 8])
708
709 def test_0_1_2_3_4_5_6_7_8_9(self) -> None:
710 self.subtest_register_levels([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
711
712 def test_0_1_2_3_4_5_6_7_8_9_10(self) -> None:
713 self.subtest_register_levels([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
714
715 def test_0_2(self) -> None:
716 self.subtest_register_levels([0, 2])
717
718 def test_0_3(self) -> None:
719 self.subtest_register_levels([0, 3])
720
721 def test_0_4(self) -> None:
722 self.subtest_register_levels([0, 4])
723
724 def test_0_5(self) -> None:
725 self.subtest_register_levels([0, 5])
726
727 def test_0_6(self) -> None:
728 self.subtest_register_levels([0, 6])
729
730 def test_0_7(self) -> None:
731 self.subtest_register_levels([0, 7])
732
733 def test_0_8(self) -> None:
734 self.subtest_register_levels([0, 8])
735
736 def test_0_9(self) -> None:
737 self.subtest_register_levels([0, 9])
738
739 def test_0_10(self) -> None:
740 self.subtest_register_levels([0, 10])
741
742 if __name__ == '__main__':
743 unittest.main()