1 # SPDX-License-Identifier: LGPL-2-or-later
2 """TestRunner class, part of the Test API
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>
14 * https://bugs.libre-soc.org/show_bug.cgi?id=363
15 * https://bugs.libre-soc.org/show_bug.cgi?id=686#c51
18 from unittest
.mock
import Mock
19 from nmigen
import Module
, ClockSignal
20 from copy
import copy
, deepcopy
21 from pprint
import pformat
23 from elftools
.elf
.elffile
import ELFFile
# for isinstance
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
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
35 from openpower
.decoder
.power_decoder2
import PowerDecode2
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
44 class SimRunner(StateRunner
):
45 """SimRunner: Implements methods for the setup, preparation, and
46 running of tests using ISACaller simulation
49 def __init__(self
, dut
, m
, pspec
,
51 use_syscall_emu
=False):
52 super().__init
__("sim", SimRunner
)
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
64 def prepare_for_test(self
, test
):
69 def run_test(self
, instructions
, gen
, insncode
):
70 """run_sim_state - runs an ISACaller simulation
73 dut
, test
, simdec2
= self
.dut
, self
.test
, self
.simdec2
76 # set up the Simulator (which must track TestIssuer exactly)
77 sim
= ISA(simdec2
, test
.regs
, test
.sprs
, test
.cr
, test
.mem
,
79 initial_insns
=gen
, respect_pc
=True,
82 initial_svstate
=test
.svstate
,
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
)
89 index
= ins
= code
= 0 # variables for nonlocal
92 nonlocal index
, ins
, code
93 index
= sim
.pc
.CIA
.value
//4
94 if index
>= len(instructions
):
96 ins
, code
= instructions
[index
]
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
)
107 if isinstance(gen
, ELFFile
):
109 nonlocal index
, ins
, code
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
118 # run the loop of the instructions on the current test
120 # set up simulated instruction (in simdec2)
122 yield from sim
.setup_one()
123 except KeyError: # instruction not in imem: stop
127 # call simulated operation
130 yield from sim
.execute_one()
131 except ExitSyscallCalled
:
135 # get sim register and memory TestState, add to list
136 state
= yield from TestState("sim", sim
, dut
, code
)
137 sim_states
.append(state
)
139 log(f
"final pc: 0x{sim.pc.CIA.value:X}", kind
=LogType
.InstrInOuts
)
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
)
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.
154 ISACaller simulation can be skipped by setting run_sim=False.
156 When using an Expected state to test with, the expected state
157 is passed in with tst_data.
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,
164 use_syscall_emu
=False):
165 super().__init
__("run_all")
166 self
.test_data
= tst_data
167 self
.microwatt_mmu
= microwatt_mmu
170 self
.allow_overlap
= allow_overlap
171 self
.inorder
= inorder
172 self
.run_hdl
= run_hdl
173 self
.run_sim
= run_sim
175 self
.use_mmap_mem
= use_mmap_mem
176 self
.use_syscall_emu
= use_syscall_emu
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'
187 ldst_ifacetype
= 'test_mmu_cache_wb'
188 imem_ifacetype
= 'test_bare_wb'
190 ldst_ifacetype
= 'test_bare_wb'
191 imem_ifacetype
= 'test_bare_wb'
193 pspec
= Mock(ldst_ifacetype
=ldst_ifacetype
,
194 imem_ifacetype
=imem_ifacetype
,
204 regreduce
=not self
.allow_overlap
,
205 core_domain
="sync", # no alternative domain
207 allow_overlap
=self
.allow_overlap
,
208 inorder
=self
.inorder
,
209 mmu
=self
.microwatt_mmu
,
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
219 # allow wb_get to run
220 if self
.rom
is not None:
226 hdlrun
= self
.run_hdl(self
, m
, pspec
)
227 state_list
.append(hdlrun
)
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
)
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())
243 # nmigen Simulation - everything runs around this, so it
244 # still has to be created.
250 ###### PREPARATION PHASE AT START OF RUNNING #######
252 for runner
in state_list
:
253 yield from runner
.setup_during_test()
255 # get each test, completely reset the core, and run it
257 for test
in self
.test_data
:
258 with self
.subTest(test
.name
, **test
.subtest_args
):
260 ###### PREPARATION PHASE AT START OF TEST #######
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
))
272 for runner
in state_list
:
273 yield from runner
.prepare_for_test(test
)
275 log("running test: ", test
.name
, test
.subtest_args
,
276 kind
=LogType
.InstrInOuts
)
277 program
= test
.program
279 def format_regs(regs
):
280 # type: (list[int]) -> str
282 for i
, v
in enumerate(regs
):
285 for signed
in ("u", "i"):
286 value
= v
% (1 << sz
)
287 if signed
== "i" and \
288 value
& (1 << (sz
- 1)) != 0:
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
)
299 log("msr", "None", kind
=LogType
.InstrInOuts
)
301 log("msr", hex(test
.msr
), kind
=LogType
.InstrInOuts
)
303 def format_assembly(assembly
):
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() != "":
312 return "\n".join(out
)
313 if isinstance(program
, ELFFile
):
315 "/proc/self/fd/%d" % program
.stream
.fileno())
316 log("using program: " + f
, kind
=LogType
.InstrInOuts
)
317 instructions
= program
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
))
327 ###### RUNNING OF EACH TEST #######
328 # StateRunner.step_test()
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
337 # TODO https://bugs.libre-soc.org/show_bug.cgi?id=686#c73
343 hdl_states
= yield from hdlrun
.run_test(instructions
)
350 sim_states
= yield from simrun
.run_test(
354 ###### COMPARING THE TESTS #######
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
365 last_sim
= copy(sim_states
[-1])
367 last_sim
= copy(hdl_states
[-1])
369 last_sim
= None # err what are you doing??
373 for state
in hdl_states
:
376 # FIXME: commented until SimState has a __repr__
379 # for state in sim_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
),
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
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
)
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
)
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
))
422 ###### END OF A TEST #######
423 # StateRunner.end_test()
425 for runner
in state_list
:
426 yield from runner
.end_test() # TODO, some arguments?
428 ###### END OF EVERYTHING (but none needs doing, still call fn) ####
429 # StateRunner.cleanup()
431 for runner
in state_list
:
432 yield from runner
.cleanup() # TODO, some arguments?
434 # finally stop wb_get from going
435 if self
.rom
is not None:
439 'dec': {'base': 'dec'},
440 'bin': {'base': 'bin'},
441 'closed': {'closed': True}
446 ('state machines', 'closed', [
447 'fetch_pc_i_valid', 'fetch_pc_o_ready',
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',
454 'exec_insn_i_valid', 'exec_insn_o_ready',
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'},
461 'cia[63:0]', 'nia[63:0]', 'pc[63:0]', 'msr[63:0]',
462 'cur_pc[63:0]', 'core_core_cia[63: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]',
481 'dec2.o_svdec.reg_in[4:0]',
482 ('dec2.o_svdec.spec[2:0]', 'bin'),
483 'dec2.o_svdec.reg_out[6:0]']),
485 'dec2.in1_svdec.reg_in[4:0]',
486 ('dec2.in1_svdec.spec[2:0]', 'bin'),
487 'dec2.in1_svdec.reg_out[6:0]']),
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]'
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',
502 'core_rego[6:0]', 'core_reg1[6:0]', 'core_reg2[6:0]']),
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', [
525 'pdecode2.exc_happened',
527 'core.fus.ldst0.exc_o_happened']),
530 # PortInterface module path varies depending on MMU option
531 if self
.microwatt_mmu
:
532 pi_module
= 'core.ldst0'
534 pi_module
= 'core.fus.ldst0'
536 traces
+= [('ld/st port interface', {'submodule': pi_module
}, [
539 'ldst_port0_is_ld_i',
540 'ldst_port0_is_st_i',
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',
555 if self
.microwatt_mmu
:
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',
577 gtkname
= "issuer_simulator"
581 write_gtkw("%s.gtkw" % gtkname
,
583 traces
, styles
, module
='bench.top.issuer')
585 # add run of instructions
586 sim
.add_sync_process(process
)
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)
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")))
606 if "SIM_NO_VCD" in os
.environ
:
609 with sim
.write_vcd("%s.vcd" % gtkname
):