make tests executable
[ieee754fpu.git] / src / ieee754 / div_rem_sqrt_rsqrt / test_core.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 .core import (DivPipeCoreConfig, DivPipeCoreSetupStage,
6 DivPipeCoreCalculateStage, DivPipeCoreFinalStage,
7 DivPipeCoreOperation, DivPipeCoreInputData,
8 DivPipeCoreInterstageData, DivPipeCoreOutputData)
9 from .algorithm import (FixedUDivRemSqrtRSqrt, Fixed, Operation, div_rem,
10 fixed_sqrt, fixed_rsqrt)
11 import unittest
12 from nmigen import Module, Elaboratable
13 from nmigen.hdl.ir import Fragment
14 from nmigen.back import rtlil
15 from nmigen.back.pysim import Simulator, Delay, Tick
16
17
18 def show_fixed(bits, fract_width, bit_width):
19 fixed = Fixed.from_bits(bits, fract_width, bit_width, False)
20 return f"{str(fixed)}:{repr(fixed)}"
21
22
23 def get_core_op(alg_op):
24 if alg_op is Operation.UDivRem:
25 return DivPipeCoreOperation.UDivRem
26 if alg_op is Operation.SqrtRem:
27 return DivPipeCoreOperation.SqrtRem
28 assert alg_op is Operation.RSqrtRem
29 return DivPipeCoreOperation.RSqrtRem
30
31
32 class TestCaseData:
33 def __init__(self,
34 dividend,
35 divisor_radicand,
36 alg_op,
37 quotient_root,
38 remainder,
39 core_config):
40 self.dividend = dividend
41 self.divisor_radicand = divisor_radicand
42 self.alg_op = alg_op
43 self.quotient_root = quotient_root
44 self.remainder = remainder
45 self.core_config = core_config
46
47 @property
48 def core_op(self):
49 return get_core_op(self.alg_op)
50
51 def __str__(self):
52 bit_width = self.core_config.bit_width
53 fract_width = self.core_config.fract_width
54 dividend_str = show_fixed(dividend,
55 fract_width * 2,
56 bit_width + fract_width)
57 divisor_radicand_str = show_fixed(divisor_radicand,
58 fract_width,
59 bit_width)
60 quotient_root_str = self.show_fixed(quotient_root,
61 fract_width,
62 bit_width)
63 remainder_str = self.show_fixed(remainder,
64 fract_width * 3,
65 bit_width * 3)
66 return f"{{dividend={dividend_str}, " \
67 + f"divisor_radicand={divisor_radicand_str}, " \
68 + f"op={self.alg_op.name}, " \
69 + f"quotient_root={quotient_root_str}, " \
70 + f"remainder={remainder_str}, " \
71 + f"config={self.core_config}}}"
72
73
74 def generate_test_case(core_config, dividend, divisor_radicand, alg_op):
75 bit_width = core_config.bit_width
76 fract_width = core_config.fract_width
77 if alg_op is Operation.UDivRem:
78 if divisor_radicand == 0:
79 return
80 quotient_root, remainder = div_rem(dividend,
81 divisor_radicand,
82 bit_width * 3,
83 False)
84 remainder <<= fract_width
85 elif alg_op is Operation.SqrtRem:
86 root_remainder = fixed_sqrt(Fixed.from_bits(divisor_radicand,
87 fract_width,
88 bit_width,
89 False))
90 quotient_root = root_remainder.root.bits
91 remainder = root_remainder.remainder.bits << fract_width
92 else:
93 assert alg_op is Operation.RSqrtRem
94 if divisor_radicand == 0:
95 return
96 root_remainder = fixed_rsqrt(Fixed.from_bits(divisor_radicand,
97 fract_width,
98 bit_width,
99 False))
100 quotient_root = root_remainder.root.bits
101 remainder = root_remainder.remainder.bits
102 if quotient_root >= (1 << bit_width):
103 return
104 yield TestCaseData(dividend,
105 divisor_radicand,
106 alg_op,
107 quotient_root,
108 remainder,
109 core_config)
110
111
112 def get_test_cases(core_config,
113 dividend_range=None,
114 divisor_range=None,
115 radicand_range=None):
116 if dividend_range is None:
117 dividend_range = range(1 << (core_config.bit_width
118 + core_config.fract_width))
119 if divisor_range is None:
120 divisor_range = range(1 << core_config.bit_width)
121 if radicand_range is None:
122 radicand_range = range(1 << core_config.bit_width)
123
124 for alg_op in Operation:
125 if alg_op is Operation.UDivRem:
126 for dividend in dividend_range:
127 for divisor in divisor_range:
128 yield from generate_test_case(core_config,
129 dividend,
130 divisor,
131 alg_op)
132 else:
133 for radicand in radicand_range:
134 yield from generate_test_case(core_config,
135 dividend,
136 radicand,
137 alg_op)
138
139
140 class DivPipeCoreTestPipeline(Elaboratable):
141 def __init__(self, core_config):
142 self.setup_stage = DivPipeCoreSetupStage(core_config)
143 self.calculate_stages = [
144 DivPipeCoreCalculateStage(core_config, stage_index)
145 for stage_index in range(core_config.num_calculate_stages)]
146 self.final_stage = DivPipeCoreFinalStage(core_config)
147 self.interstage_signals = [
148 DivPipeCoreInterstageData(core_config, reset_less=True)
149 for i in range(core_config.num_calculate_stages + 1)]
150 self.i = DivPipeCoreInputData(core_config, reset_less=True)
151 self.o = DivPipeCoreOutputData(core_config, reset_less=True)
152
153 def elaborate(self, platform):
154 m = Module()
155 stages = [self.setup_stage, *self.calculate_stages, self.final_stage]
156 stage_inputs = [self.i, *self.interstage_signals]
157 stage_outputs = [*self.interstage_signals, self.o]
158 for stage, input, output in zip(stages, stage_inputs, stage_outputs):
159 stage.setup(m, input)
160 m.d.sync += output.eq(stage.process(input))
161
162 return m
163
164 def traces(self):
165 yield from self.i
166 for interstage_signal in self.interstage_signals:
167 yield from interstage_signal
168 yield from self.o
169
170
171 class TestDivPipeCore(unittest.TestCase):
172 def handle_case(self,
173 core_config,
174 dividend_range=None,
175 divisor_range=None,
176 radicand_range=None):
177 def gen_test_cases():
178 yield from get_test_cases(core_config,
179 dividend_range,
180 divisor_range,
181 radicand_range)
182 base_name = f"div_pipe_core_bit_width_{core_config.bit_width}"
183 base_name += f"_fract_width_{core_config.fract_width}"
184 base_name += f"_radix_{1 << core_config.log2_radix}"
185 with self.subTest(part="synthesize"):
186 dut = DivPipeCoreTestPipeline(core_config)
187 vl = rtlil.convert(dut, ports=[*dut.i, *dut.o])
188 with open(f"{base_name}.il", "w") as f:
189 f.write(vl)
190 dut = DivPipeCoreTestPipeline(core_config)
191 with Simulator(dut,
192 vcd_file=f"{base_name}.vcd",
193 gtkw_file=f"{base_name}.gtkw",
194 traces=[*dut.traces()]) as sim:
195 def generate_process():
196 for test_case in gen_test_cases():
197 yield dut.i.dividend.eq(test_case.dividend)
198 yield dut.i.divisor_radicand.eq(test_case.divisor_radicand)
199 yield dut.i.operation.eq(test_case.core_op)
200 yield Delay(1e-6)
201 yield Tick()
202
203 def check_process():
204 # sync with generator
205 yield
206 for _ in core_config.num_calculate_stages:
207 yield
208 yield
209
210 # now synched with generator
211 for test_case in gen_test_cases():
212 yield Delay(1e-6)
213 quotient_root = (yield dut.o.quotient_root)
214 remainder = (yield dut.o.remainder)
215 with self.subTest(test_case=str(test_case)):
216 self.assertEqual(quotient_root,
217 test_case.quotient_root)
218 self.assertEqual(remainder, test_case.remainder)
219 yield Tick()
220 sim.add_clock(2e-6)
221 sim.add_sync_process(generate_process)
222 sim.add_sync_process(check_process)
223 sim.run()
224
225 def test_bit_width_8_fract_width_4_radix_2(self):
226 self.handle_case(DivPipeCoreConfig(bit_width=8,
227 fract_width=4,
228 log2_radix=1))
229
230 # FIXME: add more test_* functions
231
232
233 if __name__ == '__main__':
234 unittest.main()