add fixme
[ieee754fpu.git] / src / ieee754 / div_rem_sqrt_rsqrt / test_core.py
1 # SPDX-License-Identifier: LGPL-2.1-or-later
2 # See Notices.txt for copyright information
3
4 from .core import (DivPipeCoreConfig, DivPipeCoreSetupStage,
5 DivPipeCoreCalculateStage, DivPipeCoreFinalStage,
6 DivPipeCoreOperation, DivPipeCoreInputData,
7 DivPipeCoreInterstageData, DivPipeCoreOutputData)
8 from .algorithm import (FixedUDivRemSqrtRSqrt, Fixed, Operation, div_rem,
9 fixed_sqrt, fixed_rsqrt)
10 import unittest
11 from nmigen import Module, Elaboratable
12 from nmigen.hdl.ir import Fragment
13 from nmigen.back import rtlil
14 from nmigen.back.pysim import Simulator, Delay, Tick
15
16
17 def show_fixed(bits, fract_width, bit_width):
18 fixed = Fixed.from_bits(bits, fract_width, bit_width, False)
19 return f"{str(fixed)}:{repr(fixed)}"
20
21
22 def get_core_op(alg_op):
23 if alg_op is Operation.UDivRem:
24 return DivPipeCoreOperation.UDivRem
25 if alg_op is Operation.SqrtRem:
26 return DivPipeCoreOperation.SqrtRem
27 assert alg_op is Operation.RSqrtRem
28 return DivPipeCoreOperation.RSqrtRem
29
30
31 class TestCaseData:
32 def __init__(self,
33 dividend,
34 divisor_radicand,
35 alg_op,
36 quotient_root,
37 remainder,
38 core_config):
39 self.dividend = dividend
40 self.divisor_radicand = divisor_radicand
41 self.alg_op = alg_op
42 self.quotient_root = quotient_root
43 self.remainder = remainder
44 self.core_config = core_config
45
46 @property
47 def core_op(self):
48 return get_core_op(self.alg_op)
49
50 def __str__(self):
51 bit_width = self.core_config.bit_width
52 fract_width = self.core_config.fract_width
53 dividend_str = show_fixed(dividend,
54 fract_width * 2,
55 bit_width + fract_width)
56 divisor_radicand_str = show_fixed(divisor_radicand,
57 fract_width,
58 bit_width)
59 quotient_root_str = self.show_fixed(quotient_root,
60 fract_width,
61 bit_width)
62 remainder_str = self.show_fixed(remainder,
63 fract_width * 3,
64 bit_width * 3)
65 return f"{{dividend={dividend_str}, " \
66 + f"divisor_radicand={divisor_radicand_str}, " \
67 + f"op={self.alg_op.name}, " \
68 + f"quotient_root={quotient_root_str}, " \
69 + f"remainder={remainder_str}, " \
70 + f"config={self.core_config}}}"
71
72
73 def generate_test_case(core_config, dividend, divisor_radicand, alg_op):
74 bit_width = core_config.bit_width
75 fract_width = core_config.fract_width
76 if alg_op is Operation.UDivRem:
77 if divisor_radicand == 0:
78 return
79 quotient_root, remainder = div_rem(dividend,
80 divisor_radicand,
81 bit_width * 3,
82 False)
83 remainder <<= fract_width
84 elif alg_op is Operation.SqrtRem:
85 root_remainder = fixed_sqrt(Fixed.from_bits(divisor_radicand,
86 fract_width,
87 bit_width,
88 False))
89 quotient_root = root_remainder.root.bits
90 remainder = root_remainder.remainder.bits << fract_width
91 else:
92 assert alg_op is Operation.RSqrtRem
93 if divisor_radicand == 0:
94 return
95 root_remainder = fixed_rsqrt(Fixed.from_bits(divisor_radicand,
96 fract_width,
97 bit_width,
98 False))
99 quotient_root = root_remainder.root.bits
100 remainder = root_remainder.remainder.bits
101 if quotient_root >= (1 << bit_width):
102 return
103 yield TestCaseData(dividend,
104 divisor_radicand,
105 alg_op,
106 quotient_root,
107 remainder,
108 core_config)
109
110
111 def get_test_cases(core_config,
112 dividend_range=None,
113 divisor_range=None,
114 radicand_range=None):
115 if dividend_range is None:
116 dividend_range = range(1 << (core_config.bit_width
117 + core_config.fract_width))
118 if divisor_range is None:
119 divisor_range = range(1 << core_config.bit_width)
120 if radicand_range is None:
121 radicand_range = range(1 << core_config.bit_width)
122
123 for alg_op in Operation:
124 if alg_op is Operation.UDivRem:
125 for dividend in dividend_range:
126 for divisor in divisor_range:
127 yield from generate_test_case(core_config,
128 dividend,
129 divisor,
130 alg_op)
131 else:
132 for radicand in radicand_range:
133 yield from generate_test_case(core_config,
134 dividend,
135 radicand,
136 alg_op)
137
138
139 class DivPipeCoreTestPipeline(Elaboratable):
140 def __init__(self, core_config):
141 self.setup_stage = DivPipeCoreSetupStage(core_config)
142 self.calculate_stages = [
143 DivPipeCoreCalculateStage(core_config, stage_index)
144 for stage_index in range(core_config.num_calculate_stages)]
145 self.final_stage = DivPipeCoreFinalStage(core_config)
146 self.interstage_signals = [
147 DivPipeCoreInterstageData(core_config, reset_less=False)
148 for i in range(core_config.num_calculate_stages + 1)]
149 self.i = DivPipeCoreInputData(core_config, reset_less=False)
150 self.o = DivPipeCoreOutputData(core_config, reset_less=False)
151
152 def elaborate(self, platform):
153 m = Module()
154 stages = [self.setup_stage, *self.calculate_stages, self.final_stage]
155 stage_inputs = [self.i, *self.interstage_signals]
156 stage_outputs = [*self.interstage_signals, self.o]
157 for stage, input, output in zip(stages, stage_inputs, stage_outputs):
158 stage.setup(m, input)
159 m.d.sync += output.eq(stage.process(input))
160
161 return m
162
163 def traces(self):
164 yield from self.i
165 for interstage_signal in self.interstage_signals:
166 yield from interstage_signal
167 yield from self.o
168
169
170 class TestDivPipeCore(unittest.TestCase):
171 def handle_case(self,
172 core_config,
173 dividend_range=None,
174 divisor_range=None,
175 radicand_range=None):
176 def gen_test_cases():
177 yield from get_test_cases(core_config,
178 dividend_range,
179 divisor_range,
180 radicand_range)
181 base_name = f"div_pipe_core_bit_width_{core_config.bit_width}"
182 base_name += f"_fract_width_{core_config.fract_width}"
183 base_name += f"_radix_{1 << core_config.log2_radix}"
184 with self.subTest(part="synthesize"):
185 dut = DivPipeCoreTestPipeline(core_config)
186 vl = rtlil.convert(dut, ports=[*dut.i, *dut.o])
187 with open(f"{base_name}.il", "w") as f:
188 f.write(vl)
189 self.fail("generated invalid rtlil") # FIXME: remove when fixed
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