16f0939bc3e4ac18c0b2829035450f7c2995c9e2
[soc.git] / src / soc / fu / div / test / test_pipe_caller.py
1 import random
2 import unittest
3 import power_instruction_analyzer as pia
4 from nmigen import Module, Signal
5 from nmigen.back.pysim import Simulator, Delay
6 from nmigen.cli import rtlil
7 from soc.decoder.power_decoder import (create_pdecode)
8 from soc.decoder.power_decoder2 import (PowerDecode2)
9 from soc.decoder.power_enums import XER_bits, Function
10 from soc.simulator.program import Program
11 from soc.decoder.isa.all import ISA
12 from soc.config.endian import bigendian
13
14 from soc.fu.test.common import (TestCase, ALUHelpers)
15 from soc.fu.div.pipeline import DivBasePipe
16 from soc.fu.div.pipe_data import DivPipeSpec, DivPipeKind
17 from typing import List
18
19
20 def log_rand(n, min_val=1):
21 logrange = random.randint(1, n)
22 return random.randint(min_val, (1 << logrange)-1)
23
24
25 def get_cu_inputs(dec2, sim):
26 """naming (res) must conform to DivFunctionUnit input regspec
27 """
28 res = {}
29
30 yield from ALUHelpers.get_sim_int_ra(res, sim, dec2) # RA
31 yield from ALUHelpers.get_sim_int_rb(res, sim, dec2) # RB
32 yield from ALUHelpers.get_sim_xer_so(res, sim, dec2) # XER.so
33
34 print("alu get_cu_inputs", res)
35
36 return res
37
38
39 def set_alu_inputs(alu, dec2, sim):
40 # TODO: see https://bugs.libre-soc.org/show_bug.cgi?id=305#c43
41 # detect the immediate here (with m.If(self.i.ctx.op.imm_data.imm_ok))
42 # and place it into data_i.b
43
44 inp = yield from get_cu_inputs(dec2, sim)
45 yield from ALUHelpers.set_int_ra(alu, dec2, inp)
46 yield from ALUHelpers.set_int_rb(alu, dec2, inp)
47
48 yield from ALUHelpers.set_xer_so(alu, dec2, inp)
49 return pia.InstructionInput(ra=inp["ra"], rb=inp["rb"], rc=0)
50
51
52 def pia_result_to_output(pia_result: pia.InstructionResult):
53 retval = {}
54 if pia_result.rt is not None:
55 retval["o"] = pia_result.rt
56 if pia_result.cr0 is not None:
57 cr0: pia.ConditionRegister = pia_result.cr0
58 v = 0
59 if cr0.lt:
60 v |= 8
61 if cr0.gt:
62 v |= 4
63 if cr0.eq:
64 v |= 2
65 if cr0.so:
66 v |= 1
67 retval["cr_a"] = v
68 if pia_result.overflow is not None:
69 overflow: pia.OverflowFlags = pia_result.overflow
70 v = 0
71 if overflow.ov:
72 v |= 1
73 if overflow.ov32:
74 v |= 2
75 retval["xer_ov"] = v
76 retval["xer_so"] = overflow.so
77 else:
78 retval["xer_ov"] = 0
79 retval["xer_so"] = 0
80 return retval
81
82
83 # This test bench is a bit different than is usual. Initially when I
84 # was writing it, I had all of the tests call a function to create a
85 # device under test and simulator, initialize the dut, run the
86 # simulation for ~2 cycles, and assert that the dut output what it
87 # should have. However, this was really slow, since it needed to
88 # create and tear down the dut and simulator for every test case.
89
90 # Now, instead of doing that, every test case in DivTestCase puts some
91 # data into the test_data list below, describing the instructions to
92 # be tested and the initial state. Once all the tests have been run,
93 # test_data gets passed to TestRunner which then sets up the DUT and
94 # simulator once, runs all the data through it, and asserts that the
95 # results match the pseudocode sim at every cycle.
96
97 # By doing this, I've reduced the time it takes to run the test suite
98 # massively. Before, it took around 1 minute on my computer, now it
99 # takes around 3 seconds
100
101
102 class DivTestCases:
103 test_data: List[TestCase]
104
105 def __init__(self):
106 self.test_data = []
107 for n, v in self.__class__.__dict__.items():
108 if n.startswith("test") and callable(v):
109 self._current_test_name = n
110 v(self)
111
112 def run_test_program(self, prog, initial_regs=None, initial_sprs=None):
113 tc = TestCase(prog, self._current_test_name,
114 initial_regs, initial_sprs)
115 self.test_data.append(tc)
116
117 def test_0_regression(self):
118 for i in range(40):
119 lst = ["divwo 3, 1, 2"]
120 initial_regs = [0] * 32
121 initial_regs[1] = 0xbc716835f32ac00c
122 initial_regs[2] = 0xcdf69a7f7042db66
123 self.run_test_program(Program(lst, bigendian), initial_regs)
124
125 def test_1_regression(self):
126 lst = ["divwo 3, 1, 2"]
127 initial_regs = [0] * 32
128 initial_regs[1] = 0x10000000000000000-4
129 initial_regs[2] = 0x10000000000000000-2
130 self.run_test_program(Program(lst, bigendian), initial_regs)
131
132 def test_2_regression(self):
133 lst = ["divwo 3, 1, 2"]
134 initial_regs = [0] * 32
135 initial_regs[1] = 0xffffffffffff9321
136 initial_regs[2] = 0xffffffffffff7012
137 self.run_test_program(Program(lst, bigendian), initial_regs)
138
139 def test_3_regression(self):
140 lst = ["divwo. 3, 1, 2"]
141 initial_regs = [0] * 32
142 initial_regs[1] = 0x1b8e32f2458746af
143 initial_regs[2] = 0x6b8aee2ccf7d62e9
144 self.run_test_program(Program(lst, bigendian), initial_regs)
145
146 def test_4_regression(self):
147 lst = ["divw 3, 1, 2"]
148 initial_regs = [0] * 32
149 initial_regs[1] = 0x1c4e6c2f3aa4a05c
150 initial_regs[2] = 0xe730c2eed6cc8dd7
151 self.run_test_program(Program(lst, bigendian), initial_regs)
152
153 def test_5_regression(self):
154 lst = ["divw 3, 1, 2",
155 "divwo. 6, 4, 5"]
156 initial_regs = [0] * 32
157 initial_regs[1] = 0x1c4e6c2f3aa4a05c
158 initial_regs[2] = 0xe730c2eed6cc8dd7
159 initial_regs[4] = 0x1b8e32f2458746af
160 initial_regs[5] = 0x6b8aee2ccf7d62e9
161 self.run_test_program(Program(lst, bigendian), initial_regs)
162
163 def test_6_regression(self):
164 # CR0 not getting set properly for this one
165 # turns out that overflow is not set correctly in
166 # fu/div/output_stage.py calc_overflow
167 # https://bugs.libre-soc.org/show_bug.cgi?id=425
168 lst = ["divw. 3, 1, 2"]
169 initial_regs = [0] * 32
170 initial_regs[1] = 0x61c1cc3b80f2a6af
171 initial_regs[2] = 0x9dc66a7622c32bc0
172 self.run_test_program(Program(lst, bigendian), initial_regs)
173
174 def test_7_regression(self):
175 # https://bugs.libre-soc.org/show_bug.cgi?id=425
176 lst = ["divw. 3, 1, 2"]
177 initial_regs = [0] * 32
178 initial_regs[1] = 0xf1791627e05e8096
179 initial_regs[2] = 0xffc868bf4573da0b
180 self.run_test_program(Program(lst, bigendian), initial_regs)
181
182 def test_divw_by_zero_1(self):
183 lst = ["divw. 3, 1, 2"]
184 initial_regs = [0] * 32
185 initial_regs[1] = 0x1
186 initial_regs[2] = 0x0
187 self.run_test_program(Program(lst, bigendian), initial_regs)
188
189 def test_divw_overflow2(self):
190 lst = ["divw. 3, 1, 2"]
191 initial_regs = [0] * 32
192 initial_regs[1] = 0x80000000
193 initial_regs[2] = 0xffffffffffffffff # top bits don't seem to matter
194 self.run_test_program(Program(lst, bigendian), initial_regs)
195
196 def test_divw_overflow3(self):
197 lst = ["divw. 3, 1, 2"]
198 initial_regs = [0] * 32
199 initial_regs[1] = 0x80000000
200 initial_regs[2] = 0xffffffff
201 self.run_test_program(Program(lst, bigendian), initial_regs)
202
203 def test_divwuo_regression_1(self):
204 lst = ["divwuo. 3, 1, 2"]
205 initial_regs = [0] * 32
206 initial_regs[1] = 0x7591a398c4e32b68
207 initial_regs[2] = 0x48674ab432867d69
208 self.run_test_program(Program(lst, bigendian), initial_regs)
209
210 def test_divwuo_1(self):
211 lst = ["divwuo. 3, 1, 2"]
212 initial_regs = [0] * 32
213 initial_regs[1] = 0x50
214 initial_regs[2] = 0x2
215 self.run_test_program(Program(lst, bigendian), initial_regs)
216
217 def test_all(self):
218 instrs = []
219 for width in ("w", "d"):
220 for sign in ("", "u"):
221 for ov in ("", "o"):
222 for cnd in ("", "."):
223 instrs += ["div" + width + sign + ov + cnd,
224 "div" + width + "e" + sign + ov + cnd]
225 for sign in ("s", "u"):
226 instrs += ["mod" + sign + width]
227 test_values = [
228 0x0,
229 0x1,
230 0x2,
231 0xFFFF_FFFF_FFFF_FFFF,
232 0xFFFF_FFFF_FFFF_FFFE,
233 0x7FFF_FFFF_FFFF_FFFF,
234 0x8000_0000_0000_0000,
235 0x1234_5678_0000_0000,
236 0x1234_5678_8000_0000,
237 0x1234_5678_FFFF_FFFF,
238 0x1234_5678_7FFF_FFFF,
239 ]
240 for instr in instrs:
241 l = [f"{instr} 3, 1, 2"]
242 for ra in test_values:
243 for rb in test_values:
244 initial_regs = [0] * 32
245 initial_regs[1] = ra
246 initial_regs[2] = rb
247 prog = Program(l, bigendian)
248 self.run_test_program(prog, initial_regs)
249
250 def test_rand_divwu(self):
251 insns = ["divwu", "divwu.", "divwuo", "divwuo."]
252 for i in range(40):
253 choice = random.choice(insns)
254 lst = [f"{choice} 3, 1, 2"]
255 initial_regs = [0] * 32
256 initial_regs[1] = log_rand(32)
257 initial_regs[2] = log_rand(32)
258 self.run_test_program(Program(lst, bigendian), initial_regs)
259
260 def test_rand_divw(self):
261 insns = ["divw", "divw.", "divwo", "divwo."]
262 for i in range(40):
263 choice = random.choice(insns)
264 lst = [f"{choice} 3, 1, 2"]
265 initial_regs = [0] * 32
266 initial_regs[1] = log_rand(32)
267 initial_regs[2] = log_rand(32)
268 self.run_test_program(Program(lst, bigendian), initial_regs)
269
270
271 class TestRunner(unittest.TestCase):
272 def write_ilang(self, div_pipe_kind):
273 pspec = DivPipeSpec(id_wid=2, div_pipe_kind=div_pipe_kind)
274 alu = DivBasePipe(pspec)
275 vl = rtlil.convert(alu, ports=alu.ports())
276 with open(f"div_pipeline_{div_pipe_kind.name}.il", "w") as f:
277 f.write(vl)
278
279 def test_write_ilang_div_pipe_core(self):
280 self.write_ilang(DivPipeKind.DivPipeCore)
281
282 def test_write_ilang_fsm_div_core(self):
283 self.write_ilang(DivPipeKind.FSMDivCore)
284
285 def test_write_ilang_sim_only(self):
286 self.write_ilang(DivPipeKind.SimOnly)
287
288 def run_all(self, div_pipe_kind):
289 test_data = DivTestCases().test_data
290 m = Module()
291 comb = m.d.comb
292 instruction = Signal(32)
293
294 pdecode = create_pdecode()
295
296 m.submodules.pdecode2 = pdecode2 = PowerDecode2(pdecode)
297
298 pspec = DivPipeSpec(id_wid=2, div_pipe_kind=div_pipe_kind)
299 m.submodules.alu = alu = DivBasePipe(pspec)
300
301 comb += alu.p.data_i.ctx.op.eq_from_execute1(pdecode2.e)
302 comb += alu.n.ready_i.eq(1)
303 comb += pdecode2.dec.raw_opcode_in.eq(instruction)
304 sim = Simulator(m)
305
306 sim.add_clock(1e-6)
307
308 def process():
309 for test in test_data:
310 print(test.name)
311 program = test.program
312 with self.subTest(test.name):
313 isa_sim = ISA(pdecode2, test.regs, test.sprs, test.cr,
314 test.mem, test.msr,
315 bigendian=bigendian)
316 gen = program.generate_instructions()
317 instructions = list(
318 zip(gen, program.assembly.splitlines()))
319 yield Delay(0.1e-6)
320
321 index = isa_sim.pc.CIA.value // 4
322 while index < len(instructions):
323 ins, code = instructions[index]
324
325 print("instruction: 0x{:X}".format(ins & 0xffffffff))
326 print(code)
327 if 'XER' in isa_sim.spr:
328 so = 1 if isa_sim.spr['XER'][XER_bits['SO']] else 0
329 ov = 1 if isa_sim.spr['XER'][XER_bits['OV']] else 0
330 ov32 = 1 if isa_sim.spr['XER'][XER_bits['OV32']] else 0
331 xer_zero = not (so or ov or ov32)
332 print("before: so/ov/32", so, ov, ov32)
333 else:
334 xer_zero = True
335
336 # ask the decoder to decode this binary data (endian'd)
337 # little / big?
338 yield pdecode2.dec.bigendian.eq(bigendian)
339 yield instruction.eq(ins) # raw binary instr.
340 yield Delay(0.1e-6)
341 fn_unit = yield pdecode2.e.do.fn_unit
342 self.assertEqual(fn_unit, Function.DIV.value)
343 pia_inputs = yield from set_alu_inputs(alu, pdecode2, isa_sim)
344
345 # set valid for one cycle, propagate through pipeline...
346 yield alu.p.valid_i.eq(1)
347 yield
348 yield alu.p.valid_i.eq(0)
349
350 opname = code.split(' ')[0]
351 if xer_zero:
352 fnname = opname.replace(".", "_")
353 print(f"{fnname}({pia_inputs})")
354 pia_result = getattr(
355 pia, opname.replace(".", "_"))(pia_inputs)
356 print(f"-> {pia_result}")
357 else:
358 pia_result = None
359 yield from isa_sim.call(opname)
360 index = isa_sim.pc.CIA.value//4
361
362 vld = yield alu.n.valid_o
363 while not vld:
364 yield
365 yield Delay(0.1e-6)
366 vld = yield alu.n.valid_o
367 # bug #425 investigation
368 do = alu.pipe_end.div_out
369 ctx_op = do.i.ctx.op
370 is_32bit = yield ctx_op.is_32bit
371 is_signed = yield ctx_op.is_signed
372 quotient_root = yield do.i.core.quotient_root
373 quotient_65 = yield do.quotient_65
374 dive_abs_ov32 = yield do.i.dive_abs_ov32
375 div_by_zero = yield do.i.div_by_zero
376 quotient_neg = yield do.quotient_neg
377 print("32bit", hex(is_32bit))
378 print("signed", hex(is_signed))
379 print("quotient_root", hex(quotient_root))
380 print("quotient_65", hex(quotient_65))
381 print("div_by_zero", hex(div_by_zero))
382 print("dive_abs_ov32", hex(dive_abs_ov32))
383 print("quotient_neg", hex(quotient_neg))
384 print("")
385 yield
386
387 yield Delay(0.1e-6)
388 print("time:", sim._state.timeline.now)
389 yield from self.check_alu_outputs(alu, pdecode2, isa_sim, code, pia_result)
390
391 sim.add_sync_process(process)
392 with sim.write_vcd(f"div_simulator_{div_pipe_kind.name}.vcd",
393 f"div_simulator_{div_pipe_kind.name}.gtkw",
394 traces=[]):
395 sim.run()
396
397 def check_alu_outputs(self, alu, dec2, sim, code, pia_result):
398
399 rc = yield dec2.e.do.rc.data
400 cridx_ok = yield dec2.e.write_cr.ok
401 cridx = yield dec2.e.write_cr.data
402
403 print("check extra output", repr(code), cridx_ok, cridx)
404 if rc:
405 self.assertEqual(cridx, 0, code)
406
407 sim_o = {}
408 res = {}
409
410 yield from ALUHelpers.get_cr_a(res, alu, dec2)
411 yield from ALUHelpers.get_xer_ov(res, alu, dec2)
412 yield from ALUHelpers.get_int_o(res, alu, dec2)
413 yield from ALUHelpers.get_xer_so(res, alu, dec2)
414
415 print("res output", res)
416
417 yield from ALUHelpers.get_sim_int_o(sim_o, sim, dec2)
418 yield from ALUHelpers.get_wr_sim_cr_a(sim_o, sim, dec2)
419 yield from ALUHelpers.get_sim_xer_ov(sim_o, sim, dec2)
420 yield from ALUHelpers.get_sim_xer_so(sim_o, sim, dec2)
421
422 print("sim output", sim_o)
423
424 print("power-instruction-analyzer result:")
425 print(pia_result)
426 if pia_result is not None:
427 with self.subTest(check="pia", sim_o=sim_o, pia_result=str(pia_result)):
428 pia_o = pia_result_to_output(pia_result)
429
430 ALUHelpers.check_int_o(self, res, pia_o, code)
431 ALUHelpers.check_cr_a(self, res, pia_o,
432 "CR%d %s" % (cridx, code))
433 ALUHelpers.check_xer_ov(self, res, pia_o, code)
434 ALUHelpers.check_xer_so(self, res, pia_o, code)
435
436 with self.subTest(check="sim", sim_o=sim_o, pia_result=str(pia_result)):
437 ALUHelpers.check_int_o(self, res, sim_o, code)
438 ALUHelpers.check_cr_a(self, res, sim_o, "CR%d %s" % (cridx, code))
439 ALUHelpers.check_xer_ov(self, res, sim_o, code)
440 ALUHelpers.check_xer_so(self, res, sim_o, code)
441
442 oe = yield dec2.e.do.oe.oe
443 oe_ok = yield dec2.e.do.oe.ok
444 print("oe, oe_ok", oe, oe_ok)
445 if not oe or not oe_ok:
446 # if OE not enabled, XER SO and OV must not be activated
447 so_ok = yield alu.n.data_o.xer_so.ok
448 ov_ok = yield alu.n.data_o.xer_ov.ok
449 print("so, ov", so_ok, ov_ok)
450 self.assertEqual(ov_ok, False, code)
451 self.assertEqual(so_ok, False, code)
452
453 def test_run_div_pipe_core(self):
454 self.run_all(DivPipeKind.DivPipeCore)
455
456 def test_run_fsm_div_core(self):
457 self.run_all(DivPipeKind.FSMDivCore)
458
459 def test_run_sim_only(self):
460 self.run_all(DivPipeKind.SimOnly)
461
462
463 if __name__ == "__main__":
464 unittest.main()