From 185ae8b94ebeebbe697701c4360bdfb042ff2e49 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Fri, 11 Feb 2022 20:25:19 +0100 Subject: [PATCH] added config and better loading code to CPU base --- riscemu/CPU.py | 11 +++++-- riscemu/__main__.py | 5 ++- riscemu/priv/PrivCPU.py | 71 ++++++++++++++++++---------------------- riscemu/priv/__main__.py | 7 ++-- riscemu/types.py | 15 ++++++++- 5 files changed, 59 insertions(+), 50 deletions(-) diff --git a/riscemu/CPU.py b/riscemu/CPU.py index 2ac4548..6340495 100644 --- a/riscemu/CPU.py +++ b/riscemu/CPU.py @@ -10,13 +10,14 @@ import typing from typing import List, Type import riscemu +from . import AssemblyFileLoader, RunConfig from .MMU import MMU from .base import BinaryDataMemorySection from .colors import FMT_CPU, FMT_NONE from .debug import launch_debug_session from .exceptions import RiscemuBaseException, LaunchDebuggerException from .syscall import SyscallInterface, get_syscall_symbols -from .types import CPU +from .types import CPU, ProgramLoader if typing.TYPE_CHECKING: from .instructions.instruction_set import InstructionSet @@ -29,14 +30,14 @@ class UserModeCPU(CPU): It is initialized with a configuration and a list of instruction sets. """ - def __init__(self, instruction_sets: List[Type['riscemu.InstructionSet']]): + def __init__(self, instruction_sets: List[Type['riscemu.InstructionSet']], conf: RunConfig): """ Creates a CPU instance. :param instruction_sets: A list of instruction set classes. These must inherit from the InstructionSet class """ # setup CPU states - super().__init__(MMU(), instruction_sets) + super().__init__(MMU(), instruction_sets, conf) self.exit_code = 0 @@ -104,3 +105,7 @@ class UserModeCPU(CPU): return False self.regs.set('sp', stack_sec.base + stack_sec.size) + + @classmethod + def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]: + return [AssemblyFileLoader] diff --git a/riscemu/__main__.py b/riscemu/__main__.py index fbfe96b..1a88200 100644 --- a/riscemu/__main__.py +++ b/riscemu/__main__.py @@ -99,15 +99,14 @@ if __name__ == '__main__': ] try: - cpu = UserModeCPU(ins_to_load) + cpu = UserModeCPU(ins_to_load, cfg) opts = AssemblyFileLoader.get_options(sys.argv) for file in args.files: loader = AssemblyFileLoader.instantiate(file, opts) - cpu.load_program(loader.parse()) - # run the last loaded executable + # set up a stack cpu.setup_stack(cfg.stack_size) # launch the last loaded program diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 6fa83eb..a6d9c5a 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -3,19 +3,20 @@ RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ +import sys import time from riscemu.CPU import * from .CSR import CSR +from .ElfLoader import ElfBinaryFileLoader from .Exceptions import * -from .PrivMMU import PrivMMU +from .ImageLoader import MemoryImageLoader from .PrivRV32I import PrivRV32I from .privmodes import PrivModes -from ..IO import TextIO from ..instructions import RV32A, RV32M +from ..types import Program if typing.TYPE_CHECKING: - from riscemu import types, LoadedExecutable, LoadedInstruction from riscemu.instructions.instruction_set import InstructionSet @@ -38,21 +39,20 @@ class PrivCPU(CPU): controls the resolution of the time csr register (in nanoseconds) """ - INS_XLEN = 4 + pending_traps: List[CpuTrap] """ - Size of an instruction in memory. Should be 4, but since our loading code is shit, instruction take up - the equivalent of "1 byte" (this is actually impossible) + A list of traps which are pending to be handled """ def __init__(self, conf): - super().__init__(conf, [PrivRV32I, RV32M, RV32A]) + super().__init__(MMU(), [PrivRV32I, RV32M, RV32A], conf) # start in machine mode self.mode: PrivModes = PrivModes.MACHINE - self.syscall_int = None - self.launch_debug = False self.pending_traps: List[CpuTrap] = list() + self.exit_code = 0 + self._time_start = 0 self._time_timecmp = 0 self._time_interrupt_enabled = False @@ -63,45 +63,37 @@ class PrivCPU(CPU): # init csr self._init_csr() - def _run(self, verbose=False): + def run(self, verbose=False): if self.pc <= 0: return False - ins = None + + launch_debug = False + try: - while not self.exit: + while not self.halted: self.step(verbose) except RiscemuBaseException as ex: if isinstance(ex, LaunchDebuggerException): - self.launch_debug = True + launch_debug = True self.pc += self.INS_XLEN - if self.exit: + if self.halted: print() - print(FMT_CPU + "Program exited with code {}".format(self.exit_code) + FMT_NONE) + print(FMT_CPU + "[CPU] System halted with code {}".format(self.exit_code) + FMT_NONE) sys.exit(self.exit_code) - elif self.launch_debug: - self.launch_debug = False - launch_debug_session(self, self.mmu, self.regs, - "Launching debugger:") + + elif launch_debug: + launch_debug_session(self) if not self.debugger_active: - self._run(verbose) + self.run(verbose) else: print() - print(FMT_CPU + "Program stopped without exiting - perhaps you stopped the debugger?" + FMT_NONE) + print(FMT_CPU + "[CPU] System stopped without halting - perhaps you stopped the debugger?" + FMT_NONE) - def load(self, e: riscemu.base_types): - raise NotImplementedError("Not supported!") - - def run_loaded(self, le: 'riscemu.LoadedExecutable'): - raise NotImplementedError("Not supported!") - - def get_tokenizer(self, tokenizer_input): - raise NotImplementedError("Not supported!") - - def run(self, verbose: bool = False): + def launch(self, program: Program, 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) + self.run(self.conf.verbosity > 1 or verbose) def _init_csr(self): # set up CSR @@ -184,7 +176,7 @@ class PrivCPU(CPU): if not (len(self.pending_traps) > 0 and self.csr.get_mstatus('mie')): return # select best interrupt - # TODO: actually select based on the official ranking + # FIXME: actually select based on the official ranking trap = self.pending_traps.pop() # use the most recent trap if self.conf.verbosity > 0: print(FMT_CPU + "[CPU] taking trap {}!".format(trap) + FMT_NONE) @@ -209,7 +201,7 @@ class PrivCPU(CPU): if mtvec & 0b11 == 1: self.pc = (mtvec & 0b11111111111111111111111111111100) + (trap.code * 4) self.record_perf_profile() - if len(self._perf_counters) % 100 == 0: + if len(self._perf_counters) > 100: self.show_perf() def show_perf(self): @@ -225,11 +217,6 @@ class PrivCPU(CPU): continue cps = (cycle - cycled) / (time_ns - timed) * 1000000000 - # print(" {:03d} cycles in {:08d}ns ({:.2f} cycles/s)".format( - # cycle - cycled, - # time_ns - timed, - # cps - # )) cycled = cycle timed = time_ns cps_list.append(cps) @@ -238,3 +225,9 @@ class PrivCPU(CPU): def record_perf_profile(self): self._perf_counters.append((time.perf_counter_ns(), self.cycle)) + + @classmethod + def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]: + return [ + AssemblyFileLoader, MemoryImageLoader, ElfBinaryFileLoader + ] \ No newline at end of file diff --git a/riscemu/priv/__main__.py b/riscemu/priv/__main__.py index 36c0d13..2363de4 100644 --- a/riscemu/priv/__main__.py +++ b/riscemu/priv/__main__.py @@ -1,7 +1,6 @@ -from .PrivCPU import PrivCPU, RunConfig -from .ImageLoader import MemoryImageMMU -from .PrivMMU import LoadedElfMMU -from .ElfLoader import ElfExecutable +from .PrivCPU import PrivCPU +from .ElfLoader import ElfBinaryFileLoader +from .ImageLoader import MemoryImageLoader import sys diff --git a/riscemu/types.py b/riscemu/types.py index e2895f0..e2f7065 100644 --- a/riscemu/types.py +++ b/riscemu/types.py @@ -15,6 +15,7 @@ from collections import defaultdict from dataclasses import dataclass from typing import Dict, List, Optional, Tuple, Set, Union, Iterator, Callable, Type +from . import RunConfig from .colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE, FMT_RED, FMT_BOLD from .exceptions import ParseException from .helpers import format_bytes, get_section_base_name @@ -371,9 +372,13 @@ class CPU(ABC): instructions: Dict[str, Callable[[Instruction], None]] instruction_sets: Set['InstructionSet'] - def __init__(self, mmu: 'MMU', instruction_sets: List[Type['InstructionSet']]): + # configuration + conf: RunConfig + + def __init__(self, mmu: 'MMU', instruction_sets: List[Type['InstructionSet']], conf: RunConfig): self.mmu = mmu self.regs = Registers() + self.conf = conf self.instruction_sets = set() self.instructions = dict() @@ -433,3 +438,11 @@ class CPU(ABC): self.pc = program.entrypoint self.run(verbose) + + @classmethod + @abstractmethod + def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]: + pass + + def get_best_loader_for(self, file_name: str) -> Type[ProgramLoader]: + return max(self.get_loaders(), key=lambda ld: ld.can_parse(file_name))