fixed priv start code, added tests
parent
4f1c73df9e
commit
cd5795bb74
@ -0,0 +1,73 @@
|
|||||||
|
import contextlib
|
||||||
|
import os
|
||||||
|
from abc import abstractmethod
|
||||||
|
from tempfile import NamedTemporaryFile
|
||||||
|
from typing import Optional, Union, Tuple
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from riscemu import CPU, UserModeCPU, InstructionSetDict, RunConfig
|
||||||
|
from riscemu.types import Program
|
||||||
|
|
||||||
|
|
||||||
|
class EndToEndTest(TestCase):
|
||||||
|
|
||||||
|
def __init__(self, cpu: Optional[CPU] = None):
|
||||||
|
super().__init__()
|
||||||
|
if cpu is None:
|
||||||
|
cpu = UserModeCPU(InstructionSetDict.values(), RunConfig())
|
||||||
|
self.cpu = cpu
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_source(self) -> Tuple[str, Union[bytes, str, bytearray]]:
|
||||||
|
"""
|
||||||
|
This method returns the source code of the program
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_run_program(self):
|
||||||
|
"""
|
||||||
|
Runs the program and verifies output
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
with self.with_source_file() as names:
|
||||||
|
fname, orig_name = names
|
||||||
|
loader = self.cpu.get_best_loader_for(fname)
|
||||||
|
self.program = loader.instantiate(fname, loader.get_options([])).parse()
|
||||||
|
self._change_program_file_name(self.program, orig_name)
|
||||||
|
self.cpu.load_program(self.program)
|
||||||
|
self.after_program_load(self.program)
|
||||||
|
if isinstance(self.cpu, UserModeCPU):
|
||||||
|
self.cpu.setup_stack()
|
||||||
|
try:
|
||||||
|
self.cpu.launch(self.program)
|
||||||
|
except Exception as ex:
|
||||||
|
if self.is_exception_expected(ex):
|
||||||
|
pass
|
||||||
|
raise ex
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def with_source_file(self):
|
||||||
|
name, content = self.get_source()
|
||||||
|
if isinstance(content, str):
|
||||||
|
f = NamedTemporaryFile('w', suffix=name, delete=False)
|
||||||
|
else:
|
||||||
|
f = NamedTemporaryFile('wb', suffix=name, delete=False)
|
||||||
|
f.write(content)
|
||||||
|
f.flush()
|
||||||
|
f.close()
|
||||||
|
try:
|
||||||
|
yield f.name, name
|
||||||
|
finally:
|
||||||
|
os.unlink(f.name)
|
||||||
|
|
||||||
|
def after_program_load(self, program):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def is_exception_expected(self, ex: Exception) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _change_program_file_name(self, program: Program, new_name: str):
|
||||||
|
program.name = new_name
|
||||||
|
for sec in program.sections:
|
||||||
|
sec.owner = new_name
|
@ -0,0 +1,77 @@
|
|||||||
|
from riscemu.colors import FMT_ERROR, FMT_NONE, FMT_BOLD, FMT_GREEN
|
||||||
|
from riscemu.exceptions import ASSERT_LEN
|
||||||
|
from riscemu.helpers import int_from_bytes
|
||||||
|
from riscemu.instructions import InstructionSet
|
||||||
|
from riscemu.types import Instruction, CPU
|
||||||
|
from riscemu.decoder import RISCV_REGS
|
||||||
|
|
||||||
|
FMT_SUCCESS = FMT_GREEN + FMT_BOLD
|
||||||
|
|
||||||
|
|
||||||
|
def assert_equals(ins: Instruction, cpu: CPU):
|
||||||
|
a, b = (get_arg_from_ins(ins, i, cpu) for i in (0, 2))
|
||||||
|
return a == b
|
||||||
|
|
||||||
|
|
||||||
|
def assert_equals_mem(ins: Instruction, cpu: CPU):
|
||||||
|
a, b = (get_arg_from_ins(ins, i, cpu) for i in (0, 2))
|
||||||
|
a = cpu.mmu.read_int(a)
|
||||||
|
return a == b
|
||||||
|
|
||||||
|
|
||||||
|
def assert_in(ins: Instruction, cpu: CPU):
|
||||||
|
a = get_arg_from_ins(ins, 0, cpu)
|
||||||
|
others = [get_arg_from_ins(ins, i, cpu) for i in range(2, len(ins.args))]
|
||||||
|
return a in others
|
||||||
|
|
||||||
|
|
||||||
|
def _not(func):
|
||||||
|
def test(ins: Instruction, cpu: CPU):
|
||||||
|
return not func(ins, cpu)
|
||||||
|
|
||||||
|
return test
|
||||||
|
|
||||||
|
|
||||||
|
def get_arg_from_ins(ins: Instruction, num: int, cpu: CPU):
|
||||||
|
a = ins.args[num]
|
||||||
|
if a in RISCV_REGS:
|
||||||
|
return cpu.regs.get(a)
|
||||||
|
return ins.get_imm(num)
|
||||||
|
|
||||||
|
|
||||||
|
assert_ops = {
|
||||||
|
'==': assert_equals,
|
||||||
|
'!=': _not(assert_equals),
|
||||||
|
'in': assert_in,
|
||||||
|
'not_in': _not(assert_in),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestIS(InstructionSet):
|
||||||
|
def __init__(self, cpu: 'CPU'):
|
||||||
|
print('[Test] loading testing ISA, this is only meant for running testcases and is not part of the RISC-V ISA!')
|
||||||
|
self.failed = False
|
||||||
|
super().__init__(cpu)
|
||||||
|
|
||||||
|
def instruction_assert(self, ins: Instruction):
|
||||||
|
if len(ins.args) < 3:
|
||||||
|
print(FMT_ERROR + '[Test] Unknown assert statement: {}'.format(ins) + FMT_NONE)
|
||||||
|
return
|
||||||
|
op = ins.args[1]
|
||||||
|
if op not in assert_ops:
|
||||||
|
print(FMT_ERROR + '[Test] Unknown operation statement: {} in {}'.format(op, ins) + FMT_NONE)
|
||||||
|
return
|
||||||
|
|
||||||
|
if assert_ops[op](ins, self.cpu):
|
||||||
|
print(FMT_SUCCESS + '[TestCase] 🟢 passed assertion {}'.format(ins))
|
||||||
|
else:
|
||||||
|
print(FMT_ERROR + '[TestCase] 🔴 failed assertion {}'.format(ins))
|
||||||
|
self.cpu.halted = True
|
||||||
|
self.failed = True
|
||||||
|
|
||||||
|
def instruction_fail(self, ins: Instruction):
|
||||||
|
print(FMT_ERROR + '[TestCase] 🔴 reached fail instruction! {}'.format(ins))
|
||||||
|
self.cpu.halted = True
|
||||||
|
self.failed = True
|
||||||
|
|
||||||
|
def assert_mem(self, ins: Instruction):
|
@ -0,0 +1,53 @@
|
|||||||
|
from riscemu import AssemblyFileLoader
|
||||||
|
from riscemu.colors import *
|
||||||
|
|
||||||
|
FMT_SUCCESS = FMT_GREEN + FMT_BOLD
|
||||||
|
|
||||||
|
def run_test(path: str):
|
||||||
|
from riscemu import CPU, UserModeCPU, RunConfig
|
||||||
|
from riscemu.instructions import InstructionSetDict
|
||||||
|
from test.test_isa import TestIS
|
||||||
|
import os
|
||||||
|
|
||||||
|
fname = os.path.basename(path)
|
||||||
|
|
||||||
|
ISAs = list(InstructionSetDict.values())
|
||||||
|
ISAs.append(TestIS)
|
||||||
|
|
||||||
|
cpu = UserModeCPU(ISAs, RunConfig())
|
||||||
|
try:
|
||||||
|
program = AssemblyFileLoader(path, {}).parse()
|
||||||
|
cpu.load_program(program)
|
||||||
|
cpu.launch(program)
|
||||||
|
except Exception as ex:
|
||||||
|
print(FMT_ERROR + '[Test] 🔴 failed with exception "{}" ({})'.format(ex, fname) + FMT_NONE)
|
||||||
|
raise ex
|
||||||
|
|
||||||
|
if cpu.halted:
|
||||||
|
for isa in cpu.instruction_sets:
|
||||||
|
if isinstance(isa, TestIS):
|
||||||
|
if not isa.failed:
|
||||||
|
print(FMT_SUCCESS + '[Test] 🟢 successful {}'.format(fname) + FMT_NONE)
|
||||||
|
return not isa.failed
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
|
||||||
|
successes = 0
|
||||||
|
failures = 0
|
||||||
|
ttl = 0
|
||||||
|
|
||||||
|
for path in glob.glob(f'{os.path.dirname(__file__)}/*.asm'):
|
||||||
|
print(FMT_BLUE + '[Test] running testcase ' + os.path.basename(path) + FMT_NONE)
|
||||||
|
ttl += 1
|
||||||
|
if run_test(path):
|
||||||
|
successes += 1
|
||||||
|
else:
|
||||||
|
failures += 1
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
|||||||
|
.data
|
||||||
|
|
||||||
|
data:
|
||||||
|
.word 0xFFFFFFFF, 0x0000FFFF, 0xFF00FF00, 0x7FFFFFFF
|
||||||
|
|
||||||
|
.text
|
||||||
|
ebreak
|
@ -0,0 +1,20 @@
|
|||||||
|
.text
|
||||||
|
|
||||||
|
main:
|
||||||
|
addi a0, zero, main
|
||||||
|
addi a1, zero, main
|
||||||
|
addi t0, zero, 1000
|
||||||
|
assert a0, ==, 0x100
|
||||||
|
1:
|
||||||
|
addi a1, a1, 1
|
||||||
|
blt a1, t0, 1b
|
||||||
|
sub a1, a1, a0
|
||||||
|
j 1f
|
||||||
|
addi a1, zero, 0
|
||||||
|
fail
|
||||||
|
1:
|
||||||
|
assert a1, ==, 744
|
||||||
|
add a0, zero, a1 ; set exit code to a1
|
||||||
|
addi a7, zero, SCALL_EXIT ; exit syscall code
|
||||||
|
scall
|
||||||
|
fail
|
Loading…
Reference in New Issue