-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathrun_unit_tests.py
More file actions
executable file
·101 lines (83 loc) · 4.03 KB
/
run_unit_tests.py
File metadata and controls
executable file
·101 lines (83 loc) · 4.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#!/usr/bin/env python3
#
# Runs the RV32UI, RV32MI, RV32UM, RV32UA, and RV32UC RISC-V unit tests
#
import sys, os, glob, argparse
from elftools.elf.elffile import ELFFile
from machine import Machine
from cpu import CPU
from ram import SafeRAMOffset
def parse_args():
parser = argparse.ArgumentParser(description="RISC-V Test Runner")
parser.add_argument("executable", nargs="?", help="exexutable test file")
args = parser.parse_args(sys.argv[1:])
return args
def get_symbol_address(filename, symbol_name):
with open(filename, 'rb') as f:
elf = ELFFile(f)
symtab = elf.get_section_by_name('.symtab')
if symtab is None:
raise Exception("No symbol table found")
for symbol in symtab.iter_symbols():
if symbol.name == symbol_name:
return symbol.entry['st_value']
raise Exception(f"Symbol {symbol_name} not found")
# MAIN
if __name__ == '__main__':
args = parse_args()
# If the path of a test executable is provided on the command line, run *only* that test,
# otherwise run them all.
if args.executable is None:
test_rv32ui_fnames = [fname for fname in glob.glob('riscv-tests/isa/rv32ui-p-*') if not '.dump' in fname]
test_rv32mi_fnames = [fname for fname in glob.glob('riscv-tests/isa/rv32mi-p-*') if not '.dump' in fname]
test_rv32um_fnames = [fname for fname in glob.glob('riscv-tests/isa/rv32um-p-*') if not '.dump' in fname]
test_rv32ua_fnames = [fname for fname in glob.glob('riscv-tests/isa/rv32ua-p-*') if not '.dump' in fname]
test_rv32uc_fnames = [fname for fname in glob.glob('riscv-tests/isa/rv32uc-p-*') if not '.dump' in fname]
test_fname_list = test_rv32ui_fnames + test_rv32mi_fnames + test_rv32um_fnames + test_rv32ua_fnames + test_rv32uc_fnames
else:
test_fname_list = [ args.executable ]
# loop over tests
for test_fname in test_fname_list:
# Instantiate CPU + RAM + machine + syscall handler
ram = SafeRAMOffset(1024*1024, base_addr=0x8000_0000) # RAM base and entry point at 0x8000_0000
cpu = CPU(ram, rvc_enabled=True) # Enable RVC for tests that use compressed instructions
machine = Machine(cpu, ram, rvc=True) # Enable RVC for tests that use compressed instructions
# Load ELF file of test
machine.load_elf(test_fname)
# get address of variable (tohost) used to communicate test results
tohost_addr = get_symbol_address(test_fname, "tohost")
ram.store_word(tohost_addr, 0xFFFFFFFF) # store sentinel value
# RUN
while True:
# Check PC alignment before
if cpu.pc & 0x1:
cpu.trap(cause=0, mtval=cpu.pc) # Instruction address misaligned
cpu.pc = cpu.next_pc
continue
# Fetch
inst_low = ram.load_half(cpu.pc, signed=False)
if (inst_low & 0x3) == 0x3:
# 32-bit instruction: fetch upper 16 bits
inst_high = ram.load_half(cpu.pc + 2, signed=False)
inst = inst_low | (inst_high << 16)
else:
# 16-bit compressed instruction
inst = inst_low
cpu.execute(inst)
cpu.pc = cpu.next_pc
# if sentinel value has been overwritten, the test is over
if ram.load_word(tohost_addr) != 0xFFFFFFFF:
break
# Load and check test result
test_result = ram.load_word(tohost_addr)
result_str = "PASS" if test_result == 1 else f"FAIL (test #{test_result >> 1})"
# Output test result
if test_result != 1:
print(f"Test {os.path.basename(test_fname):<30}: {result_str}")
print(f" tohost value: 0x{test_result:08X}")
print(f" Final PC: 0x{cpu.pc:08X}")
print(f" mepc: 0x{cpu.csrs[0x341]:08X}")
print(f" mcause: 0x{cpu.csrs[0x342]:08X}")
print(f" mtval: 0x{cpu.csrs[0x343]:08X}")
else:
print(f"Test {os.path.basename(test_fname):<30}: {result_str}")