DivPipeCore tests pass; still need to add more tests
[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 obj = FixedUDivRemSqrtRSqrt(dividend,
79 divisor_radicand,
80 alg_op,
81 bit_width,
82 fract_width,
83 core_config.log2_radix)
84 obj.calculate()
85 yield TestCaseData(dividend,
86 divisor_radicand,
87 alg_op,
88 obj.quotient_root,
89 obj.remainder,
90 core_config)
91
92
93 def get_test_cases(core_config,
94 dividends=None,
95 divisors=None,
96 radicands=None):
97 if dividends is None:
98 dividends = range(1 << (core_config.bit_width
99 + core_config.fract_width))
100 else:
101 assert isinstance(dividends, list)
102 if divisors is None:
103 divisors = range(1 << core_config.bit_width)
104 else:
105 assert isinstance(divisors, list)
106 if radicands is None:
107 radicands = range(1 << core_config.bit_width)
108 else:
109 assert isinstance(radicands, list)
110
111 for alg_op in Operation:
112 if alg_op is Operation.UDivRem:
113 for dividend in dividends:
114 for divisor in divisors:
115 yield from generate_test_case(core_config,
116 dividend,
117 divisor,
118 alg_op)
119 else:
120 for radicand in radicands:
121 yield from generate_test_case(core_config,
122 0,
123 radicand,
124 alg_op)
125
126
127 class DivPipeCoreTestPipeline(Elaboratable):
128 def __init__(self, core_config, sync):
129 self.setup_stage = DivPipeCoreSetupStage(core_config)
130 self.calculate_stages = [
131 DivPipeCoreCalculateStage(core_config, stage_index)
132 for stage_index in range(core_config.num_calculate_stages)]
133 self.final_stage = DivPipeCoreFinalStage(core_config)
134 self.interstage_signals = [
135 DivPipeCoreInterstageData(core_config, reset_less=True)
136 for i in range(core_config.num_calculate_stages + 1)]
137 self.i = DivPipeCoreInputData(core_config, reset_less=True)
138 self.o = DivPipeCoreOutputData(core_config, reset_less=True)
139 self.sync = sync
140
141 def elaborate(self, platform):
142 m = Module()
143 stages = [self.setup_stage, *self.calculate_stages, self.final_stage]
144 stage_inputs = [self.i, *self.interstage_signals]
145 stage_outputs = [*self.interstage_signals, self.o]
146 for stage, input, output in zip(stages, stage_inputs, stage_outputs):
147 stage.setup(m, input)
148 assignments = output.eq(stage.process(input))
149 if self.sync:
150 m.d.sync += assignments
151 else:
152 m.d.comb += assignments
153 return m
154
155 def traces(self):
156 yield from self.i
157 # for interstage_signal in self.interstage_signals:
158 # yield from interstage_signal
159 yield from self.o
160
161
162 class TestDivPipeCore(unittest.TestCase):
163 def handle_case(self,
164 core_config,
165 dividends=None,
166 divisors=None,
167 radicands=None,
168 sync=True):
169 if dividends is not None:
170 dividends = list(dividends)
171 if divisors is not None:
172 divisors = list(divisors)
173 if radicands is not None:
174 radicands = list(radicands)
175
176 def gen_test_cases():
177 yield from get_test_cases(core_config,
178 dividends,
179 divisors,
180 radicands)
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, sync)
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 dut = DivPipeCoreTestPipeline(core_config, sync)
190 with Simulator(dut,
191 vcd_file=open(f"{base_name}.vcd", "w"),
192 gtkw_file=open(f"{base_name}.gtkw", "w"),
193 traces=[*dut.traces()]) as sim:
194 def generate_process():
195 for test_case in gen_test_cases():
196 yield Tick()
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(int(test_case.core_op))
200 yield Delay(0.9e-6)
201
202 def check_process():
203 # sync with generator
204 if sync:
205 yield
206 for _ in range(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 Tick()
213 yield Delay(0.9e-6)
214 quotient_root = (yield dut.o.quotient_root)
215 remainder = (yield dut.o.remainder)
216 with self.subTest(test_case=str(test_case)):
217 self.assertEqual(quotient_root,
218 test_case.quotient_root)
219 self.assertEqual(remainder, test_case.remainder)
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 dividends=[*range(1 << 8),
230 *range(1 << 8, 1 << 12, 1 << 4)],
231 sync=False)
232
233 def test_bit_width_2_fract_width_1_radix_2(self):
234 self.handle_case(DivPipeCoreConfig(bit_width=2,
235 fract_width=1,
236 log2_radix=1),
237 sync=False)
238
239 # FIXME: add more test_* functions
240
241
242 if __name__ == '__main__':
243 unittest.main()