fixed priv start code, added tests
This commit is contained in:
parent
4f1c73df9e
commit
cd5795bb74
@ -17,7 +17,7 @@ class MemoryImageLoader(ProgramLoader):
|
||||
|
||||
@classmethod
|
||||
def can_parse(cls, source_path: str) -> float:
|
||||
if source_path.split('.')[-1] == '.img':
|
||||
if source_path.split('.')[-1] == 'img':
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
@ -11,6 +11,7 @@ from .CSR import CSR
|
||||
from .ElfLoader import ElfBinaryFileLoader
|
||||
from .Exceptions import *
|
||||
from .ImageLoader import MemoryImageLoader
|
||||
from .PrivMMU import PrivMMU
|
||||
from .PrivRV32I import PrivRV32I
|
||||
from .privmodes import PrivModes
|
||||
from ..instructions import RV32A, RV32M
|
||||
@ -45,7 +46,7 @@ class PrivCPU(CPU):
|
||||
"""
|
||||
|
||||
def __init__(self, conf):
|
||||
super().__init__(MMU(), [PrivRV32I, RV32M, RV32A], conf)
|
||||
super().__init__(PrivMMU(), [PrivRV32I, RV32M, RV32A], conf)
|
||||
# start in machine mode
|
||||
self.mode: PrivModes = PrivModes.MACHINE
|
||||
|
||||
@ -90,11 +91,17 @@ class PrivCPU(CPU):
|
||||
print()
|
||||
print(FMT_CPU + "[CPU] System stopped without halting - perhaps you stopped the debugger?" + FMT_NONE)
|
||||
|
||||
def launch(self, program: Program, verbose: bool = False):
|
||||
def launch(self, program: Optional[Program] = None, verbose: bool = False):
|
||||
print(FMT_CPU + '[CPU] Started running from 0x{:08X} ({})'.format(self.pc, "kernel") + FMT_NONE)
|
||||
self._time_start = time.perf_counter_ns() // self.TIME_RESOLUTION_NS
|
||||
|
||||
self.run(self.conf.verbosity > 1 or verbose)
|
||||
|
||||
def load_program(self, program: Program):
|
||||
if program.name == 'kernel':
|
||||
self.pc = program.entrypoint
|
||||
super().load_program(program)
|
||||
|
||||
def _init_csr(self):
|
||||
# set up CSR
|
||||
self.csr = CSR()
|
||||
@ -230,4 +237,4 @@ class PrivCPU(CPU):
|
||||
def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]:
|
||||
return [
|
||||
AssemblyFileLoader, MemoryImageLoader, ElfBinaryFileLoader
|
||||
]
|
||||
]
|
||||
|
@ -1,3 +1,5 @@
|
||||
from riscemu import RunConfig
|
||||
from riscemu.types import Program
|
||||
from .PrivCPU import PrivCPU
|
||||
from .ElfLoader import ElfBinaryFileLoader
|
||||
from .ImageLoader import MemoryImageLoader
|
||||
@ -9,26 +11,27 @@ if __name__ == '__main__':
|
||||
|
||||
parser = argparse.ArgumentParser(description='RISC-V privileged architecture emulator', prog='riscemu')
|
||||
|
||||
parser.add_argument('--kernel', type=str, help='Kernel elf loaded with user programs', nargs='?')
|
||||
parser.add_argument('--image', type=str, help='Memory image containing kernel', nargs='?')
|
||||
parser.add_argument('--debug-exceptions', help='Launch the interactive debugger when an exception is generated', action='store_true')
|
||||
parser.add_argument('source', type=str,
|
||||
help='Compiled RISC-V ELF file or memory image containing compiled RISC-V ELF files', nargs='+')
|
||||
parser.add_argument('--debug-exceptions', help='Launch the interactive debugger when an exception is generated',
|
||||
action='store_true')
|
||||
|
||||
parser.add_argument('-v', '--verbose', help="Verbosity level (can be used multiple times)", action='count', default=0)
|
||||
parser.add_argument('-v', '--verbose', help="Verbosity level (can be used multiple times)", action='count',
|
||||
default=0)
|
||||
|
||||
args = parser.parse_args()
|
||||
mmu = None
|
||||
|
||||
if args.kernel is not None:
|
||||
mmu = LoadedElfMMU(ElfExecutable(args.kernel))
|
||||
elif args.image is not None:
|
||||
mmu = MemoryImageMMU(args.image)
|
||||
|
||||
if mmu is None:
|
||||
print("You must specify one of --kernel or --image for running in privilege mode!")
|
||||
sys.exit(1)
|
||||
|
||||
cpu = PrivCPU(RunConfig(verbosity=args.verbose, debug_on_exception=args.debug_exceptions), mmu)
|
||||
cpu.run()
|
||||
|
||||
cpu = PrivCPU(RunConfig(verbosity=args.verbose, debug_on_exception=args.debug_exceptions))
|
||||
|
||||
for source_path in args.source:
|
||||
loader = max((loader for loader in cpu.get_loaders()), key=lambda l: l.can_parse(source_path))
|
||||
argv, opts = loader.get_options(sys.argv)
|
||||
program = loader.instantiate(source_path, opts).parse()
|
||||
if isinstance(program, Program):
|
||||
cpu.load_program(program)
|
||||
else:
|
||||
program_iter = program
|
||||
for program in program_iter:
|
||||
cpu.load_program(program)
|
||||
|
||||
cpu.launch()
|
||||
|
0
test/end_to_end/__init__.py
Normal file
0
test/end_to_end/__init__.py
Normal file
73
test/end_to_end/end_to_end_test.py
Normal file
73
test/end_to_end/end_to_end_test.py
Normal file
@ -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
|
77
test/test_isa.py
Normal file
77
test/test_isa.py
Normal file
@ -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):
|
53
test/testcases/__main__.py
Normal file
53
test/testcases/__main__.py
Normal file
@ -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
|
||||
|
||||
|
||||
|
7
test/testcases/half-loads.asm
Normal file
7
test/testcases/half-loads.asm
Normal file
@ -0,0 +1,7 @@
|
||||
.data
|
||||
|
||||
data:
|
||||
.word 0xFFFFFFFF, 0x0000FFFF, 0xFF00FF00, 0x7FFFFFFF
|
||||
|
||||
.text
|
||||
ebreak
|
20
test/testcases/symbols.asm
Normal file
20
test/testcases/symbols.asm
Normal file
@ -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
Block a user