SimRunner: support running an ELFFile
[openpower-isa.git] / src / openpower / test / runner.py
1 # SPDX-License-Identifier: LGPL-2-or-later
2 """TestRunner class, part of the Test API
3
4 SPDX-License: LGPLv2+
5
6 Copyright (C) 2020,2021 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
7 Copyright (C) 2021 Kyle Lehman <klehman9@comcast.net>
8 Copyright (C) 2021 Jonathan Neuschäfer <j.neuschaefer@gmx.net>
9 Copyright (C) 2021 Tobias Platen <tplaten@posteo.de>
10 Copyright (C) 2021 Cesar Strauss <cestrauss@gmail.com>
11
12 related bugs:
13
14 * https://bugs.libre-soc.org/show_bug.cgi?id=363
15 * https://bugs.libre-soc.org/show_bug.cgi?id=686#c51
16 """
17
18 from unittest.mock import Mock
19 from nmigen import Module, ClockSignal
20 from copy import copy, deepcopy
21 from pprint import pformat
22 import os
23 from elftools.elf.elffile import ELFFile # for isinstance
24
25 # NOTE: to use cxxsim, export NMIGEN_SIM_MODE=cxxsim from the shell
26 # Also, check out the cxxsim nmigen branch, and latest yosys from git
27 from nmutil.sim_tmp_alternative import Simulator, Settle
28
29 from nmutil.formaltest import FHDLTestCase
30 from nmutil.gtkw import write_gtkw
31 from openpower.decoder.isa.all import ISA
32 from openpower.decoder.isa.caller import ExitSyscallCalled
33 from openpower.endian import bigendian
34
35 from openpower.decoder.power_decoder2 import PowerDecode2
36
37 from nmutil.util import wrap
38 from openpower.test.wb_get import wb_get
39 import openpower.test.wb_get as wbget
40 from openpower.test.state import TestState, StateRunner, ExpectedState
41 from openpower.util import log, LogType
42
43
44 class SimRunner(StateRunner):
45 """SimRunner: Implements methods for the setup, preparation, and
46 running of tests using ISACaller simulation
47 """
48
49 def __init__(self, dut, m, pspec,
50 use_mmap_mem=False,
51 use_syscall_emu=False):
52 super().__init__("sim", SimRunner)
53 self.dut = dut
54
55 self.mmu = pspec.mmu == True
56 fp_en = pspec.fp_en == True
57 regreduce_en = pspec.regreduce_en == True
58 self.simdec2 = simdec2 = PowerDecode2(
59 None, regreduce_en=regreduce_en, fp_en=fp_en)
60 m.submodules.simdec2 = simdec2 # pain in the neck
61 self.use_mmap_mem = use_mmap_mem
62 self.use_syscall_emu = use_syscall_emu
63
64 def prepare_for_test(self, test):
65 self.test = test
66 if False:
67 yield
68
69 def run_test(self, instructions, gen, insncode):
70 """run_sim_state - runs an ISACaller simulation
71 """
72
73 dut, test, simdec2 = self.dut, self.test, self.simdec2
74 sim_states = []
75
76 # set up the Simulator (which must track TestIssuer exactly)
77 sim = ISA(simdec2, test.regs, test.sprs, test.cr, test.mem,
78 test.msr,
79 initial_insns=gen, respect_pc=True,
80 disassembly=insncode,
81 bigendian=bigendian,
82 initial_svstate=test.svstate,
83 mmu=self.mmu,
84 fpregfile=test.fpregs,
85 initial_fpscr=test.initial_fpscr,
86 use_mmap_mem=self.use_mmap_mem,
87 use_syscall_emu=self.use_syscall_emu)
88
89 index = ins = code = 0 # variables for nonlocal
90
91 def next_insn():
92 nonlocal index, ins, code
93 index = sim.pc.CIA.value//4
94 if index >= len(instructions):
95 return False
96 ins, code = instructions[index]
97
98 # extra new-line so it's easier to visually separate each
99 # instruction in output
100 log("\n0x%04X: %08X %s" % (sim.pc.CIA.value,
101 ins % (1 << 32), code),
102 kind=LogType.InstrInOuts)
103
104 log(index, code)
105 return True
106
107 if isinstance(gen, ELFFile):
108 def next_insn():
109 nonlocal index, ins, code
110 index = code = None
111 ins = sim.imem.ld(sim.pc.CIA.value, width=4, swap=False,
112 check_in_mem=True, instr_fetch=True)
113 ins_str = "None" if ins is None else "%08X" % ins
114 log("\n0x%04X: %s" % (sim.pc.CIA.value, ins_str),
115 kind=LogType.InstrInOuts)
116 return not sim.halted
117
118 # run the loop of the instructions on the current test
119 while next_insn():
120 # set up simulated instruction (in simdec2)
121 try:
122 yield from sim.setup_one()
123 except KeyError: # instruction not in imem: stop
124 break
125 yield Settle()
126
127 # call simulated operation
128 log("sim", code)
129 try:
130 yield from sim.execute_one()
131 except ExitSyscallCalled:
132 break
133 yield Settle()
134
135 # get sim register and memory TestState, add to list
136 state = yield from TestState("sim", sim, dut, code)
137 sim_states.append(state)
138
139 log(f"final pc: 0x{sim.pc.CIA.value:X}", kind=LogType.InstrInOuts)
140
141 if self.dut.allow_overlap:
142 # get last state, at end of run
143 state = yield from TestState("sim", sim, dut, code)
144 sim_states.append(state)
145
146 return sim_states
147
148
149 class TestRunnerBase(FHDLTestCase):
150 """TestRunnerBase: Sets up and executes the running of tests
151 contained in tst_data. run_hdl (if provided) is an HDLRunner
152 object. If not provided, hdl simulation is skipped.
153
154 ISACaller simulation can be skipped by setting run_sim=False.
155
156 When using an Expected state to test with, the expected state
157 is passed in with tst_data.
158 """
159
160 def __init__(self, tst_data, microwatt_mmu=False, rom=None,
161 svp64=True, run_hdl=None, run_sim=True,
162 allow_overlap=False, inorder=False, fp=False,
163 use_mmap_mem=False,
164 use_syscall_emu=False):
165 super().__init__("run_all")
166 self.test_data = tst_data
167 self.microwatt_mmu = microwatt_mmu
168 self.rom = rom
169 self.svp64 = svp64
170 self.allow_overlap = allow_overlap
171 self.inorder = inorder
172 self.run_hdl = run_hdl
173 self.run_sim = run_sim
174 self.fp = fp
175 self.use_mmap_mem = use_mmap_mem
176 self.use_syscall_emu = use_syscall_emu
177
178 def run_all(self):
179 m = Module()
180 comb = m.d.comb
181 if self.microwatt_mmu:
182 # do not wire these up to anything if wb_get is to be used
183 if self.rom is not None:
184 ldst_ifacetype = 'mmu_cache_wb'
185 imem_ifacetype = 'mmu_cache_wb'
186 else:
187 ldst_ifacetype = 'test_mmu_cache_wb'
188 imem_ifacetype = 'test_bare_wb'
189 else:
190 ldst_ifacetype = 'test_bare_wb'
191 imem_ifacetype = 'test_bare_wb'
192
193 pspec = Mock(ldst_ifacetype=ldst_ifacetype,
194 imem_ifacetype=imem_ifacetype,
195 addr_wid=64,
196 mask_wid=8,
197 XLEN=64,
198 imem_reg_wid=64,
199 # wb_data_width=32,
200 use_pll=False,
201 nocore=False,
202 xics=False,
203 gpio=False,
204 regreduce=not self.allow_overlap,
205 core_domain="sync", # no alternative domain
206 svp64=self.svp64,
207 allow_overlap=self.allow_overlap,
208 inorder=self.inorder,
209 mmu=self.microwatt_mmu,
210 reg_wid=64,
211 fp_en=self.fp)
212
213 ###### SETUP PHASE #######
214 # Determine the simulations needed and add to state_list
215 # for setup and running
216 # The methods contained in the respective Runner classes are
217 # called using this list when possible
218
219 # allow wb_get to run
220 if self.rom is not None:
221 wbget.stop = False
222
223 state_list = []
224
225 if self.run_hdl:
226 hdlrun = self.run_hdl(self, m, pspec)
227 state_list.append(hdlrun)
228
229 if self.run_sim:
230 simrun = SimRunner(self, m, pspec,
231 use_mmap_mem=self.use_mmap_mem,
232 use_syscall_emu=self.use_syscall_emu)
233 state_list.append(simrun)
234
235 # run core clock at same rate as test clock
236 # XXX this has to stay here! TODO, work out why,
237 # but Simulation-only fails without it
238 intclk = ClockSignal("coresync")
239 comb += intclk.eq(ClockSignal())
240 dbgclk = ClockSignal("dbgsync")
241 comb += dbgclk.eq(ClockSignal())
242
243 # nmigen Simulation - everything runs around this, so it
244 # still has to be created.
245 sim = Simulator(m)
246 sim.add_clock(1e-6)
247
248 def process():
249
250 ###### PREPARATION PHASE AT START OF RUNNING #######
251
252 for runner in state_list:
253 yield from runner.setup_during_test()
254
255 # get each test, completely reset the core, and run it
256
257 for test in self.test_data:
258 with self.subTest(test.name, **test.subtest_args):
259
260 ###### PREPARATION PHASE AT START OF TEST #######
261
262 # HACK: if there is test memory and wb_get is in use,
263 # overwrite (reset) the wb_get memory dictionary with
264 # the test's memory contents (oh, and put the wb_get
265 # memory back in as well)
266 self.default_mem.clear()
267 if self.rom is not None:
268 self.default_mem.update(deepcopy(self.rom))
269 if test.mem is not None:
270 self.default_mem.update(deepcopy(test.mem))
271
272 for runner in state_list:
273 yield from runner.prepare_for_test(test)
274
275 log("running test: ", test.name, test.subtest_args,
276 kind=LogType.InstrInOuts)
277 program = test.program
278
279 def format_regs(regs):
280 # type: (list[int]) -> str
281 out = []
282 for i, v in enumerate(regs):
283 values = ""
284 for sz in (32, 64):
285 for signed in ("u", "i"):
286 value = v % (1 << sz)
287 if signed == "i" and \
288 value & (1 << (sz - 1)) != 0:
289 value -= 1 << sz
290 values += f" {signed}{sz}:{value}"
291 out.append(f"r{i} = 0x{v:X} {values}")
292 return "\n".join(out)
293 log("regs:", format_regs(test.regs),
294 kind=LogType.InstrInOuts)
295 log("sprs", test.sprs, kind=LogType.InstrInOuts)
296 log("cr", test.cr, kind=LogType.InstrInOuts)
297 log("mem", test.mem)
298 if test.msr is None:
299 log("msr", "None", kind=LogType.InstrInOuts)
300 else:
301 log("msr", hex(test.msr), kind=LogType.InstrInOuts)
302
303 def format_assembly(assembly):
304 # type: (str) -> str
305 pc = 0
306 out = []
307 for line in assembly.splitlines():
308 out.append(f"pc=0x{pc:04X}: {line}")
309 if not line.startswith(".set ") and \
310 line.partition('#')[0].strip() != "":
311 pc += 4
312 return "\n".join(out)
313 if isinstance(program, ELFFile):
314 f = os.readlink(
315 "/proc/self/fd/%d" % program.stream.fileno())
316 log("using program: " + f, kind=LogType.InstrInOuts)
317 instructions = program
318 gen = program
319 insncode = None
320 else:
321 log("assembly:\n" + format_assembly(program.assembly),
322 kind=LogType.InstrInOuts)
323 gen = list(program.generate_instructions())
324 insncode = program.assembly.splitlines()
325 instructions = list(zip(gen, insncode))
326
327 ###### RUNNING OF EACH TEST #######
328 # StateRunner.step_test()
329
330 # Run two tests (TODO, move these to functions)
331 # * first the Simulator, collate a batch of results
332 # * then the HDL, likewise
333 # (actually, the other way round because running
334 # Simulator somehow modifies the test state!)
335 # * finally, compare all the results
336
337 # TODO https://bugs.libre-soc.org/show_bug.cgi?id=686#c73
338
339 ##########
340 # 1. HDL
341 ##########
342 if self.run_hdl:
343 hdl_states = yield from hdlrun.run_test(instructions)
344
345 ##########
346 # 2. Simulator
347 ##########
348
349 if self.run_sim:
350 sim_states = yield from simrun.run_test(
351 instructions, gen,
352 insncode)
353
354 ###### COMPARING THE TESTS #######
355
356 ###############
357 # 3. Compare
358 ###############
359
360 # TODO: here just grab one entry from list_of_sim_runners
361 # (doesn't matter which one, honestly)
362 # TODO https://bugs.libre-soc.org/show_bug.cgi?id=686#c73
363
364 if self.run_sim:
365 last_sim = copy(sim_states[-1])
366 elif self.run_hdl:
367 last_sim = copy(hdl_states[-1])
368 else:
369 last_sim = None # err what are you doing??
370
371 if self.run_hdl:
372 log("hdl_states")
373 for state in hdl_states:
374 log(state)
375
376 # FIXME: commented until SimState has a __repr__
377 # if self.run_sim:
378 # log("sim_states")
379 # for state in sim_states:
380 # log(state)
381
382 # compare the states
383 if self.run_hdl and self.run_sim:
384 # if allow_overlap is enabled, because allow_overlap
385 # can commit out-of-order, only compare the last ones
386 if self.allow_overlap:
387 log("allow_overlap: truncating %d %d "
388 "states to last" % (len(sim_states),
389 len(hdl_states)))
390 sim_states = sim_states[-1:]
391 hdl_states = hdl_states[-1:]
392 sim_states[-1].dump_state_tofile()
393 log("allow_overlap: last hdl_state")
394 hdl_states[-1].dump_state_tofile()
395 for simstate, hdlstate in zip(sim_states, hdl_states):
396 simstate.compare(hdlstate) # register check
397 simstate.compare_mem(hdlstate) # memory check
398
399 # if no expected, create /tmp/case_name.py with code
400 # setting expected state to last_sim
401 if test.expected is None:
402 last_sim.dump_state_tofile(test.name, test.test_file)
403
404 # compare against expected results
405 if test.expected is not None:
406 # have to put these in manually
407 test.expected.to_test = test.expected
408 test.expected.dut = self
409 test.expected.state_type = "expected"
410 test.expected.code = 0
411 # do actual comparison, against last item
412 last_sim.compare(test.expected)
413
414 # check number of instructions run (sanity)
415 if self.run_hdl and self.run_sim:
416 n_hdl = len(hdl_states)
417 n_sim = len(sim_states)
418 self.assertTrue(n_hdl == n_sim,
419 "number of instructions %d %d "
420 "run not the same" % (n_hdl, n_sim))
421
422 ###### END OF A TEST #######
423 # StateRunner.end_test()
424
425 for runner in state_list:
426 yield from runner.end_test() # TODO, some arguments?
427
428 ###### END OF EVERYTHING (but none needs doing, still call fn) ####
429 # StateRunner.cleanup()
430
431 for runner in state_list:
432 yield from runner.cleanup() # TODO, some arguments?
433
434 # finally stop wb_get from going
435 if self.rom is not None:
436 wbget.stop = True
437
438 styles = {
439 'dec': {'base': 'dec'},
440 'bin': {'base': 'bin'},
441 'closed': {'closed': True}
442 }
443
444 traces = [
445 'clk',
446 ('state machines', 'closed', [
447 'fetch_pc_i_valid', 'fetch_pc_o_ready',
448 'fetch_fsm_state',
449 'fetch_insn_o_valid', 'fetch_insn_i_ready',
450 'pred_insn_i_valid', 'pred_insn_o_ready',
451 'fetch_predicate_state',
452 'pred_mask_o_valid', 'pred_mask_i_ready',
453 'issue_fsm_state',
454 'exec_insn_i_valid', 'exec_insn_o_ready',
455 'exec_fsm_state',
456 'exec_pc_o_valid', 'exec_pc_i_ready',
457 'insn_done', 'core_stop_o', 'pc_i_ok', 'pc_changed',
458 'is_last', 'dec2.no_out_vec']),
459 {'comment': 'fetch and decode'},
460 (None, 'dec', [
461 'cia[63:0]', 'nia[63:0]', 'pc[63:0]', 'msr[63:0]',
462 'cur_pc[63:0]', 'core_core_cia[63:0]']),
463 'raw_insn_i[31:0]',
464 'raw_opcode_in[31:0]', 'insn_type', 'dec2.dec2_exc_happened',
465 ('svp64 decoding', 'closed', [
466 'svp64_rm[23:0]', ('dec2.extra[8:0]', 'bin'),
467 'dec2.sv_rm_dec.mode', 'dec2.sv_rm_dec.predmode',
468 'dec2.sv_rm_dec.ptype_in',
469 'dec2.sv_rm_dec.dstpred[2:0]', 'dec2.sv_rm_dec.srcpred[2:0]',
470 'dstmask[63:0]', 'srcmask[63:0]',
471 'dregread[4:0]', 'dinvert',
472 'sregread[4:0]', 'sinvert',
473 'core.int.pred__addr[4:0]', 'core.int.pred__data_o[63:0]',
474 'core.int.pred__ren']),
475 ('register augmentation', 'dec', 'closed', [
476 {'comment': 'v3.0b registers'},
477 'dec2.dec_o.RT[4:0]',
478 'dec2.dec_a.RA[4:0]',
479 'dec2.dec_b.RB[4:0]',
480 ('Rdest', [
481 'dec2.o_svdec.reg_in[4:0]',
482 ('dec2.o_svdec.spec[2:0]', 'bin'),
483 'dec2.o_svdec.reg_out[6:0]']),
484 ('Rsrc1', [
485 'dec2.in1_svdec.reg_in[4:0]',
486 ('dec2.in1_svdec.spec[2:0]', 'bin'),
487 'dec2.in1_svdec.reg_out[6:0]']),
488 ('Rsrc1', [
489 'dec2.in2_svdec.reg_in[4:0]',
490 ('dec2.in2_svdec.spec[2:0]', 'bin'),
491 'dec2.in2_svdec.reg_out[6:0]']),
492 {'comment': 'SVP64 registers'},
493 'dec2.rego[6:0]', 'dec2.reg1[6:0]', 'dec2.reg2[6:0]'
494 ]),
495 {'comment': 'svp64 context'},
496 'core_core_vl[6:0]', 'core_core_maxvl[6:0]',
497 'core_core_srcstep[6:0]', 'next_srcstep[6:0]',
498 'core_core_dststep[6:0]',
499 {'comment': 'issue and execute'},
500 'core.core_core_insn_type',
501 (None, 'dec', [
502 'core_rego[6:0]', 'core_reg1[6:0]', 'core_reg2[6:0]']),
503 'issue_i', 'busy_o',
504 {'comment': 'dmi'},
505 'dbg.dmi_req_i', 'dbg.dmi_ack_o',
506 {'comment': 'instruction memory'},
507 'imem.sram.rdport.memory(0)[63:0]',
508 {'comment': 'registers'},
509 # match with soc.regfile.regfiles.IntRegs port names
510 'core.int.rp_src1.memory(0)[63:0]',
511 'core.int.rp_src1.memory(1)[63:0]',
512 'core.int.rp_src1.memory(2)[63:0]',
513 'core.int.rp_src1.memory(3)[63:0]',
514 'core.int.rp_src1.memory(4)[63:0]',
515 'core.int.rp_src1.memory(5)[63:0]',
516 'core.int.rp_src1.memory(6)[63:0]',
517 'core.int.rp_src1.memory(7)[63:0]',
518 'core.int.rp_src1.memory(9)[63:0]',
519 'core.int.rp_src1.memory(10)[63:0]',
520 'core.int.rp_src1.memory(13)[63:0]',
521 # Exceptions: see list archive for description of the chain
522 # http://lists.libre-soc.org/pipermail/libre-soc-dev/2021-December/004220.html
523 ('exceptions', 'closed', [
524 'exc_happened',
525 'pdecode2.exc_happened',
526 'core.exc_happened',
527 'core.fus.ldst0.exc_o_happened']),
528 ]
529
530 # PortInterface module path varies depending on MMU option
531 if self.microwatt_mmu:
532 pi_module = 'core.ldst0'
533 else:
534 pi_module = 'core.fus.ldst0'
535
536 traces += [('ld/st port interface', {'submodule': pi_module}, [
537 'oper_r__insn_type',
538 'oper_r__msr[63:0]',
539 'ldst_port0_is_ld_i',
540 'ldst_port0_is_st_i',
541 'ldst_port0_busy_o',
542 'ldst_port0_addr_i[47:0]',
543 'ldst_port0_addr_i_ok',
544 'ldst_port0_addr_ok_o',
545 'ldst_port0_exc_happened',
546 'ldst_port0_st_data_i[63:0]',
547 'ldst_port0_st_data_i_ok',
548 'ldst_port0_ld_data_o[63:0]',
549 'ldst_port0_ld_data_o_ok',
550 'ldst_port0_msr_pr',
551 'exc_o_happened',
552 'cancel'
553 ])]
554
555 if self.microwatt_mmu:
556 traces += [
557 {'comment': 'microwatt_mmu'},
558 'core.fus.mmu0.alu_mmu0.illegal',
559 'core.fus.mmu0.alu_mmu0.debug0[3:0]',
560 'core.fus.mmu0.alu_mmu0.mmu.state',
561 'core.fus.mmu0.alu_mmu0.mmu.pid[31:0]',
562 'core.fus.mmu0.alu_mmu0.mmu.prtbl[63:0]',
563 {'comment': 'wishbone_memory'},
564 'core.l0.pimem.bus__ack',
565 'core.l0.pimem.bus__adr[4:0]',
566 'core.l0.pimem.bus__bte',
567 'core.l0.pimem.bus__cti',
568 'core.l0.pimem.bus__cyc',
569 'core.l0.pimem.bus__dat_r[63:0]',
570 'core.l0.pimem.bus__dat_w[63:0]',
571 'core.l0.pimem.bus__dat_err',
572 'core.l0.pimem.bus__dat_sel[7:0]',
573 'core.l0.pimem.bus__dat_stb',
574 'core.l0.pimem.bus__dat_we',
575 ]
576
577 gtkname = "issuer_simulator"
578 if self.rom:
579 gtkname += "_mmu"
580
581 write_gtkw("%s.gtkw" % gtkname,
582 "%s.vcd" % gtkname,
583 traces, styles, module='bench.top.issuer')
584
585 # add run of instructions
586 sim.add_sync_process(process)
587
588 # ARGH oh whoops. TODO, core is not passed in!
589 # urrr... work out a hacky-way to sort this (access run_hdl
590 # core directly for now)
591
592 # optionally, if a wishbone-based ROM is passed in, run that as an
593 # extra emulated process
594 self.default_mem = {}
595 if self.rom is not None:
596 log("TestRunner with MMU ROM")
597 log(pformat(self.rom))
598 dcache = hdlrun.issuer.core.fus.fus["mmu0"].alu.dcache
599 icache = hdlrun.issuer.core.fus.fus["mmu0"].alu.icache
600 self.default_mem = deepcopy(self.rom)
601 sim.add_sync_process(wrap(wb_get(dcache.bus,
602 self.default_mem, "DCACHE")))
603 sim.add_sync_process(wrap(wb_get(icache.ibus,
604 self.default_mem, "ICACHE")))
605
606 if "SIM_NO_VCD" in os.environ:
607 sim.run()
608 else:
609 with sim.write_vcd("%s.vcd" % gtkname):
610 sim.run()