2 # SPDX-License-Identifier: LGPL-2.1-or-later
3 # See Notices.txt for copyright information
5 from ieee754
.div_rem_sqrt_rsqrt
.core
import (DivPipeCoreConfig
,
7 DivPipeCoreCalculateStage
, DivPipeCoreFinalStage
,
8 DivPipeCoreOperation
, DivPipeCoreInputData
,
9 DivPipeCoreInterstageData
, DivPipeCoreOutputData
)
10 from ieee754
.div_rem_sqrt_rsqrt
.algorithm
import (FixedUDivRemSqrtRSqrt
,
11 Fixed
, Operation
, div_rem
,
12 fixed_sqrt
, fixed_rsqrt
)
14 from nmigen
import Module
, Elaboratable
, Signal
15 from nmigen
.hdl
.ir
import Fragment
16 from nmigen
.back
import rtlil
17 from nmigen
.back
.pysim
import Simulator
, Delay
, Tick
18 from itertools
import chain
22 def show_fixed(bits
, fract_width
, bit_width
):
23 fixed
= Fixed
.from_bits(bits
, fract_width
, bit_width
, False)
24 return f
"{str(fixed)}:{repr(fixed)}"
27 def get_core_op(alg_op
):
28 if alg_op
is Operation
.UDivRem
:
29 return DivPipeCoreOperation
.UDivRem
30 if alg_op
is Operation
.SqrtRem
:
31 return DivPipeCoreOperation
.SqrtRem
32 assert alg_op
is Operation
.RSqrtRem
33 return DivPipeCoreOperation
.RSqrtRem
37 __test__
= False # make pytest ignore class
46 self
.dividend
= dividend
47 self
.divisor_radicand
= divisor_radicand
49 self
.quotient_root
= quotient_root
50 self
.remainder
= remainder
51 self
.core_config
= core_config
55 return get_core_op(self
.alg_op
)
58 bit_width
= self
.core_config
.bit_width
59 fract_width
= self
.core_config
.fract_width
60 dividend_str
= show_fixed(self
.dividend
,
62 bit_width
+ fract_width
)
63 divisor_radicand_str
= show_fixed(self
.divisor_radicand
,
66 quotient_root_str
= show_fixed(self
.quotient_root
,
69 remainder_str
= show_fixed(self
.remainder
,
72 return f
"{{dividend={dividend_str}, " \
73 + f
"divisor_radicand={divisor_radicand_str}, " \
74 + f
"op={self.alg_op.name}, " \
75 + f
"quotient_root={quotient_root_str}, " \
76 + f
"remainder={remainder_str}, " \
77 + f
"config={self.core_config}}}"
80 def generate_test_case(core_config
, dividend
, divisor_radicand
, alg_op
):
81 bit_width
= core_config
.bit_width
82 fract_width
= core_config
.fract_width
83 obj
= FixedUDivRemSqrtRSqrt(dividend
,
88 core_config
.log2_radix
)
90 yield TestCaseData(dividend
,
98 def shifted_ints(total_bits
, int_bits
):
99 """ Generate a sequence like a generalized binary version of A037124.
101 See https://oeis.org/A037124
103 Generates the sequence of all non-negative integers ``n`` in ascending
104 order with no repeats where ``n < (1 << total_bits) and n == (v << i)``
105 where ``i`` is a non-negative integer and ``v`` is a non-negative
106 integer less than ``1 << int_bits``.
109 while n
< (1 << total_bits
):
111 if n
< (1 << int_bits
):
114 n
+= 1 << (n
.bit_length() - int_bits
)
117 def partitioned_ints(bit_width
):
118 """ Get ints with all 1s on one side and 0s on the other. """
119 for i
in range(bit_width
):
120 yield (-1 << i
) & ((1 << bit_width
) - 1)
121 yield (1 << (i
+ 1)) - 1
124 class TestShiftedInts(unittest
.TestCase
):
129 0x004, 0x005, 0x006, 0x007,
130 0x008, 0x009, 0x00A, 0x00B, 0x00C, 0x00D, 0x00E, 0x00F,
131 0x010, 0x012, 0x014, 0x016, 0x018, 0x01A, 0x01C, 0x01E,
132 0x020, 0x024, 0x028, 0x02C, 0x030, 0x034, 0x038, 0x03C,
133 0x040, 0x048, 0x050, 0x058, 0x060, 0x068, 0x070, 0x078,
134 0x080, 0x090, 0x0A0, 0x0B0, 0x0C0, 0x0D0, 0x0E0, 0x0F0,
135 0x100, 0x120, 0x140, 0x160, 0x180, 0x1A0, 0x1C0, 0x1E0,
136 0x200, 0x240, 0x280, 0x2C0, 0x300, 0x340, 0x380, 0x3C0,
137 0x400, 0x480, 0x500, 0x580, 0x600, 0x680, 0x700, 0x780,
138 0x800, 0x900, 0xA00, 0xB00, 0xC00, 0xD00, 0xE00, 0xF00]
139 self
.assertEqual(list(shifted_ints(12, 4)), expected
)
142 def get_test_cases(core_config
,
146 if dividends
is None:
147 dividend_width
= core_config
.bit_width
+ core_config
.fract_width
148 dividends
= [*shifted_ints(dividend_width
,
149 max(3, core_config
.log2_radix
)),
150 *partitioned_ints(dividend_width
)]
152 assert isinstance(dividends
, list)
154 divisors
= [*shifted_ints(core_config
.bit_width
,
155 max(3, core_config
.log2_radix
)),
156 *partitioned_ints(core_config
.bit_width
)]
158 assert isinstance(divisors
, list)
159 if radicands
is None:
160 radicands
= [*shifted_ints(core_config
.bit_width
, 5),
161 *partitioned_ints(core_config
.bit_width
)]
163 assert isinstance(radicands
, list)
165 for alg_op
in reversed(Operation
): # put UDivRem at end
166 if get_core_op(alg_op
) not in core_config
.supported
:
168 if alg_op
is Operation
.UDivRem
:
169 for dividend
in dividends
:
170 for divisor
in divisors
:
171 yield from generate_test_case(core_config
,
176 for radicand
in radicands
:
177 yield from generate_test_case(core_config
,
183 class DivPipeCoreTestPipeline(Elaboratable
):
184 def __init__(self
, core_config
, sync
):
185 self
.setup_stage
= DivPipeCoreSetupStage(core_config
)
186 self
.calculate_stages
= [
187 DivPipeCoreCalculateStage(core_config
, stage_index
)
188 for stage_index
in range(core_config
.n_stages
)]
189 self
.final_stage
= DivPipeCoreFinalStage(core_config
)
190 self
.interstage_signals
= [
191 DivPipeCoreInterstageData(core_config
, reset_less
=True)
192 for i
in range(core_config
.n_stages
+ 1)]
193 self
.i
= DivPipeCoreInputData(core_config
, reset_less
=True)
194 self
.o
= DivPipeCoreOutputData(core_config
, reset_less
=True)
197 def elaborate(self
, platform
):
199 stages
= [self
.setup_stage
, *self
.calculate_stages
, self
.final_stage
]
200 stage_inputs
= [self
.i
, *self
.interstage_signals
]
201 stage_outputs
= [*self
.interstage_signals
, self
.o
]
202 for stage
, input, output
in zip(stages
, stage_inputs
, stage_outputs
):
203 stage
.setup(m
, input)
204 assignments
= output
.eq(stage
.process(input))
206 m
.d
.sync
+= assignments
208 m
.d
.comb
+= assignments
213 # for interstage_signal in self.interstage_signals:
214 # yield from interstage_signal
218 def trace_process(process
, prefix
="trace:", silent
=False):
220 if inspect
.isgeneratorfunction(process
):
227 command
= proc
.send(response
)
229 print(prefix
, command
)
230 except StopIteration:
232 except Exception as e
:
234 print(prefix
, "raised:", e
)
236 response
= (yield command
)
238 print(prefix
, "->", response
)
242 class TestDivPipeCore(unittest
.TestCase
):
243 def handle_config(self
,
247 if test_cases
is None:
248 test_cases
= get_test_cases(core_config
)
249 test_cases
= list(test_cases
)
250 base_name
= f
"test_div_pipe_core_bit_width_{core_config.bit_width}"
251 base_name
+= f
"_fract_width_{core_config.fract_width}"
252 base_name
+= f
"_radix_{1 << core_config.log2_radix}"
255 if core_config
.supported
!= frozenset(DivPipeCoreOperation
):
257 DivPipeCoreOperation
.UDivRem
: "div",
258 DivPipeCoreOperation
.SqrtRem
: "sqrt",
259 DivPipeCoreOperation
.RSqrtRem
: "rsqrt",
261 # loop using iter(DivPipeCoreOperation) to maintain order
262 for op
in DivPipeCoreOperation
:
263 if op
in core_config
.supported
:
264 base_name
+= f
"_{name_map[op]}"
267 with self
.subTest(part
="synthesize"):
268 dut
= DivPipeCoreTestPipeline(core_config
, sync
)
269 vl
= rtlil
.convert(dut
, ports
=[*dut
.i
, *dut
.o
])
270 with
open(f
"{base_name}.il", "w") as f
:
272 dut
= DivPipeCoreTestPipeline(core_config
, sync
)
274 with sim
.write_vcd(vcd_file
=open(f
"{base_name}.vcd", "w"),
275 gtkw_file
=open(f
"{base_name}.gtkw", "w"),
276 traces
=[*dut
.traces()]):
277 def generate_process():
278 for test_case
in test_cases
:
281 yield dut
.i
.dividend
.eq(test_case
.dividend
)
282 yield dut
.i
.divisor_radicand
.eq(test_case
.divisor_radicand
)
283 yield dut
.i
.operation
.eq(int(test_case
.core_op
))
290 # sync with generator
293 for _
in range(core_config
.n_stages
):
299 # now synched with generator
300 for test_case
in test_cases
:
306 quotient_root
= (yield dut
.o
.quotient_root
)
307 remainder
= (yield dut
.o
.remainder
)
308 with self
.subTest(test_case
=str(test_case
)):
309 self
.assertEqual(quotient_root
,
310 test_case
.quotient_root
,
312 self
.assertEqual(remainder
, test_case
.remainder
,
317 sim
.add_process(trace_process(generate_process
, "generate:", silent
=silent
))
318 sim
.add_process(trace_process(check_process
, "check:", silent
=silent
))
321 def test_bit_width_2_fract_width_1_radix_2_comb(self
):
322 self
.handle_config(DivPipeCoreConfig(bit_width
=2,
327 def test_bit_width_2_fract_width_1_radix_2(self
):
328 self
.handle_config(DivPipeCoreConfig(bit_width
=2,
332 def test_bit_width_8_fract_width_4_radix_2_comb(self
):
333 self
.handle_config(DivPipeCoreConfig(bit_width
=8,
338 def test_bit_width_8_fract_width_4_radix_2(self
):
339 self
.handle_config(DivPipeCoreConfig(bit_width
=8,
343 def test_bit_width_8_fract_width_4_radix_4_comb(self
):
344 self
.handle_config(DivPipeCoreConfig(bit_width
=8,
349 def test_bit_width_8_fract_width_4_radix_4(self
):
350 self
.handle_config(DivPipeCoreConfig(bit_width
=8,
354 def test_bit_width_8_fract_width_4_radix_4_div_only(self
):
355 supported
= (DivPipeCoreOperation
.UDivRem
,)
356 self
.handle_config(DivPipeCoreConfig(bit_width
=8,
359 supported
=supported
))
361 def test_bit_width_8_fract_width_4_radix_4_comb_div_only(self
):
362 supported
= (DivPipeCoreOperation
.UDivRem
,)
363 self
.handle_config(DivPipeCoreConfig(bit_width
=8,
366 supported
=supported
),
369 @unittest.skip("really slow")
370 def test_bit_width_32_fract_width_24_radix_8_comb(self
):
371 self
.handle_config(DivPipeCoreConfig(bit_width
=32,
376 @unittest.skip("really slow")
377 def test_bit_width_32_fract_width_24_radix_8(self
):
378 self
.handle_config(DivPipeCoreConfig(bit_width
=32,
382 @unittest.skip("really slow")
383 def test_bit_width_32_fract_width_28_radix_8_comb(self
):
384 self
.handle_config(DivPipeCoreConfig(bit_width
=32,
389 @unittest.skip("really slow")
390 def test_bit_width_32_fract_width_28_radix_8(self
):
391 self
.handle_config(DivPipeCoreConfig(bit_width
=32,
395 # FIXME: add more test_* functions
398 if __name__
== '__main__':