switch to exact version of cython
[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 __test__ = False # make pytest ignore class
35
36 def __init__(self,
37 dividend,
38 divisor_radicand,
39 alg_op,
40 quotient_root,
41 remainder,
42 core_config):
43 self.dividend = dividend
44 self.divisor_radicand = divisor_radicand
45 self.alg_op = alg_op
46 self.quotient_root = quotient_root
47 self.remainder = remainder
48 self.core_config = core_config
49
50 @property
51 def core_op(self):
52 return get_core_op(self.alg_op)
53
54 def __str__(self):
55 bit_width = self.core_config.bit_width
56 fract_width = self.core_config.fract_width
57 dividend_str = show_fixed(self.dividend,
58 fract_width * 2,
59 bit_width + fract_width)
60 divisor_radicand_str = show_fixed(self.divisor_radicand,
61 fract_width,
62 bit_width)
63 quotient_root_str = show_fixed(self.quotient_root,
64 fract_width,
65 bit_width)
66 remainder_str = show_fixed(self.remainder,
67 fract_width * 3,
68 bit_width * 3)
69 return f"{{dividend={dividend_str}, " \
70 + f"divisor_radicand={divisor_radicand_str}, " \
71 + f"op={self.alg_op.name}, " \
72 + f"quotient_root={quotient_root_str}, " \
73 + f"remainder={remainder_str}, " \
74 + f"config={self.core_config}}}"
75
76
77 def generate_test_case(core_config, dividend, divisor_radicand, alg_op):
78 bit_width = core_config.bit_width
79 fract_width = core_config.fract_width
80 obj = FixedUDivRemSqrtRSqrt(dividend,
81 divisor_radicand,
82 alg_op,
83 bit_width,
84 fract_width,
85 core_config.log2_radix)
86 obj.calculate()
87 yield TestCaseData(dividend,
88 divisor_radicand,
89 alg_op,
90 obj.quotient_root,
91 obj.remainder,
92 core_config)
93
94
95 def shifted_ints(total_bits, int_bits):
96 """ Generate a sequence like a generalized binary version of A037124.
97
98 See https://oeis.org/A037124
99
100 Generates the sequence of all non-negative integers ``n`` in ascending
101 order with no repeats where ``n < (1 << total_bits) and n == (v << i)``
102 where ``i`` is a non-negative integer and ``v`` is a non-negative
103 integer less than ``1 << int_bits``.
104 """
105 n = 0
106 while n < (1 << total_bits):
107 yield n
108 if n < (1 << int_bits):
109 n += 1
110 else:
111 n += 1 << (n.bit_length() - int_bits)
112
113
114 def partitioned_ints(bit_width):
115 """ Get ints with all 1s on one side and 0s on the other. """
116 for i in range(bit_width):
117 yield (-1 << i) & ((1 << bit_width) - 1)
118 yield (1 << (i + 1)) - 1
119
120
121 class TestShiftedInts(unittest.TestCase):
122 def test(self):
123 expected = [0x000,
124 0x001,
125 0x002, 0x003,
126 0x004, 0x005, 0x006, 0x007,
127 0x008, 0x009, 0x00A, 0x00B, 0x00C, 0x00D, 0x00E, 0x00F,
128 0x010, 0x012, 0x014, 0x016, 0x018, 0x01A, 0x01C, 0x01E,
129 0x020, 0x024, 0x028, 0x02C, 0x030, 0x034, 0x038, 0x03C,
130 0x040, 0x048, 0x050, 0x058, 0x060, 0x068, 0x070, 0x078,
131 0x080, 0x090, 0x0A0, 0x0B0, 0x0C0, 0x0D0, 0x0E0, 0x0F0,
132 0x100, 0x120, 0x140, 0x160, 0x180, 0x1A0, 0x1C0, 0x1E0,
133 0x200, 0x240, 0x280, 0x2C0, 0x300, 0x340, 0x380, 0x3C0,
134 0x400, 0x480, 0x500, 0x580, 0x600, 0x680, 0x700, 0x780,
135 0x800, 0x900, 0xA00, 0xB00, 0xC00, 0xD00, 0xE00, 0xF00]
136 self.assertEqual(list(shifted_ints(12, 4)), expected)
137
138
139 def get_test_cases(core_config,
140 dividends=None,
141 divisors=None,
142 radicands=None):
143 if dividends is None:
144 dividend_width = core_config.bit_width + core_config.fract_width
145 dividends = [*shifted_ints(dividend_width,
146 max(3, core_config.log2_radix)),
147 *partitioned_ints(dividend_width)]
148 else:
149 assert isinstance(dividends, list)
150 if divisors is None:
151 divisors = [*shifted_ints(core_config.bit_width,
152 max(3, core_config.log2_radix)),
153 *partitioned_ints(core_config.bit_width)]
154 else:
155 assert isinstance(divisors, list)
156 if radicands is None:
157 radicands = [*shifted_ints(core_config.bit_width, 5),
158 *partitioned_ints(core_config.bit_width)]
159 else:
160 assert isinstance(radicands, list)
161
162 for alg_op in reversed(Operation): # put UDivRem at end
163 if alg_op is Operation.UDivRem:
164 for dividend in dividends:
165 for divisor in divisors:
166 yield from generate_test_case(core_config,
167 dividend,
168 divisor,
169 alg_op)
170 else:
171 for radicand in radicands:
172 yield from generate_test_case(core_config,
173 0,
174 radicand,
175 alg_op)
176
177
178 class DivPipeCoreTestPipeline(Elaboratable):
179 def __init__(self, core_config, sync):
180 self.setup_stage = DivPipeCoreSetupStage(core_config)
181 self.calculate_stages = [
182 DivPipeCoreCalculateStage(core_config, stage_index)
183 for stage_index in range(core_config.n_stages)]
184 self.final_stage = DivPipeCoreFinalStage(core_config)
185 self.interstage_signals = [
186 DivPipeCoreInterstageData(core_config, reset_less=True)
187 for i in range(core_config.n_stages + 1)]
188 self.i = DivPipeCoreInputData(core_config, reset_less=True)
189 self.o = DivPipeCoreOutputData(core_config, reset_less=True)
190 self.sync = sync
191
192 def elaborate(self, platform):
193 m = Module()
194 stages = [self.setup_stage, *self.calculate_stages, self.final_stage]
195 stage_inputs = [self.i, *self.interstage_signals]
196 stage_outputs = [*self.interstage_signals, self.o]
197 for stage, input, output in zip(stages, stage_inputs, stage_outputs):
198 stage.setup(m, input)
199 assignments = output.eq(stage.process(input))
200 if self.sync:
201 m.d.sync += assignments
202 else:
203 m.d.comb += assignments
204 return m
205
206 def traces(self):
207 yield from self.i
208 # for interstage_signal in self.interstage_signals:
209 # yield from interstage_signal
210 yield from self.o
211
212
213 class TestDivPipeCore(unittest.TestCase):
214 def handle_config(self,
215 core_config,
216 test_cases=None,
217 sync=True):
218 if test_cases is None:
219 test_cases = get_test_cases(core_config)
220 test_cases = list(test_cases)
221 base_name = f"test_div_pipe_core_bit_width_{core_config.bit_width}"
222 base_name += f"_fract_width_{core_config.fract_width}"
223 base_name += f"_radix_{1 << core_config.log2_radix}"
224 if not sync:
225 base_name += "_comb"
226 with self.subTest(part="synthesize"):
227 dut = DivPipeCoreTestPipeline(core_config, sync)
228 vl = rtlil.convert(dut, ports=[*dut.i, *dut.o])
229 with open(f"{base_name}.il", "w") as f:
230 f.write(vl)
231 dut = DivPipeCoreTestPipeline(core_config, sync)
232 with Simulator(dut,
233 vcd_file=open(f"{base_name}.vcd", "w"),
234 gtkw_file=open(f"{base_name}.gtkw", "w"),
235 traces=[*dut.traces()]) as sim:
236 def generate_process():
237 for test_case in test_cases:
238 yield Tick()
239 yield dut.i.dividend.eq(test_case.dividend)
240 yield dut.i.divisor_radicand.eq(test_case.divisor_radicand)
241 yield dut.i.operation.eq(int(test_case.core_op))
242 yield Delay(0.9e-6)
243
244 def check_process():
245 # sync with generator
246 if sync:
247 yield
248 for _ in range(core_config.n_stages):
249 yield
250 yield
251
252 # now synched with generator
253 for test_case in test_cases:
254 yield Tick()
255 yield Delay(0.9e-6)
256 quotient_root = (yield dut.o.quotient_root)
257 remainder = (yield dut.o.remainder)
258 with self.subTest(test_case=str(test_case)):
259 self.assertEqual(quotient_root,
260 test_case.quotient_root,
261 str(test_case))
262 self.assertEqual(remainder, test_case.remainder,
263 str(test_case))
264 sim.add_clock(2e-6)
265 sim.add_sync_process(generate_process)
266 sim.add_sync_process(check_process)
267 sim.run()
268
269 def test_bit_width_2_fract_width_1_radix_2_comb(self):
270 self.handle_config(DivPipeCoreConfig(bit_width=2,
271 fract_width=1,
272 log2_radix=1),
273 sync=False)
274
275 def test_bit_width_2_fract_width_1_radix_2(self):
276 self.handle_config(DivPipeCoreConfig(bit_width=2,
277 fract_width=1,
278 log2_radix=1))
279
280 def test_bit_width_8_fract_width_4_radix_2_comb(self):
281 self.handle_config(DivPipeCoreConfig(bit_width=8,
282 fract_width=4,
283 log2_radix=1),
284 sync=False)
285
286 def test_bit_width_8_fract_width_4_radix_2(self):
287 self.handle_config(DivPipeCoreConfig(bit_width=8,
288 fract_width=4,
289 log2_radix=1))
290
291 @unittest.skip("really slow")
292 def test_bit_width_32_fract_width_24_radix_8_comb(self):
293 self.handle_config(DivPipeCoreConfig(bit_width=32,
294 fract_width=24,
295 log2_radix=3),
296 sync=False)
297
298 @unittest.skip("really slow")
299 def test_bit_width_32_fract_width_24_radix_8(self):
300 self.handle_config(DivPipeCoreConfig(bit_width=32,
301 fract_width=24,
302 log2_radix=3))
303
304 @unittest.skip("really slow")
305 def test_bit_width_32_fract_width_28_radix_8_comb(self):
306 self.handle_config(DivPipeCoreConfig(bit_width=32,
307 fract_width=28,
308 log2_radix=3),
309 sync=False)
310
311 @unittest.skip("really slow")
312 def test_bit_width_32_fract_width_28_radix_8(self):
313 self.handle_config(DivPipeCoreConfig(bit_width=32,
314 fract_width=28,
315 log2_radix=3))
316
317 # FIXME: add more test_* functions
318
319
320 if __name__ == '__main__':
321 unittest.main()