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