|
|
@ -1,108 +1,64 @@
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
RiscEmu (c) 2021 Anton Lydike
|
|
|
|
RiscEmu (c) 2021-2022 Anton Lydike
|
|
|
|
|
|
|
|
|
|
|
|
SPDX-License-Identifier: MIT
|
|
|
|
SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
|
|
|
|
This file contains the CPU logic (not the individual instruction sets). See instructions/InstructionSet.py for more info
|
|
|
|
This file contains the CPU logic (not the individual instruction sets). See instructions/instruction_set.py for more info
|
|
|
|
on them.
|
|
|
|
on them.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
import sys
|
|
|
|
import typing
|
|
|
|
from typing import Tuple, List, Dict, Callable, Type
|
|
|
|
from typing import List, Type
|
|
|
|
|
|
|
|
|
|
|
|
from .types import MemoryFlags
|
|
|
|
import riscemu
|
|
|
|
from .syscall import SyscallInterface, get_syscall_symbols
|
|
|
|
|
|
|
|
from .exceptions import RiscemuBaseException, LaunchDebuggerException
|
|
|
|
|
|
|
|
from .MMU import MMU
|
|
|
|
from .MMU import MMU
|
|
|
|
from .config import RunConfig
|
|
|
|
from .base import BinaryDataMemorySection
|
|
|
|
from .registers import Registers
|
|
|
|
from .colors import FMT_CPU, FMT_NONE
|
|
|
|
from .debug import launch_debug_session
|
|
|
|
from .debug import launch_debug_session
|
|
|
|
from .colors import FMT_CPU, FMT_NONE, FMT_ERROR
|
|
|
|
from .exceptions import RiscemuBaseException, LaunchDebuggerException
|
|
|
|
|
|
|
|
from .syscall import SyscallInterface, get_syscall_symbols
|
|
|
|
import riscemu
|
|
|
|
from .types import CPU
|
|
|
|
|
|
|
|
|
|
|
|
import typing
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if typing.TYPE_CHECKING:
|
|
|
|
if typing.TYPE_CHECKING:
|
|
|
|
from . import types, LoadedExecutable, LoadedInstruction
|
|
|
|
from .instructions.instruction_set import InstructionSet
|
|
|
|
from .instructions.InstructionSet import InstructionSet
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CPU:
|
|
|
|
class UserModeCPU(CPU):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
This class represents a single CPU. It holds references to it's mmu, registers and syscall interrupt handler.
|
|
|
|
This class represents a single CPU. It holds references to it's mmu, registers and syscall interrupt handler.
|
|
|
|
|
|
|
|
|
|
|
|
It is initialized with a configuration and a list of instruction sets.
|
|
|
|
It is initialized with a configuration and a list of instruction sets.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
INS_XLEN = 4
|
|
|
|
def __init__(self, instruction_sets: List[Type['riscemu.InstructionSet']]):
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, conf: RunConfig, instruction_sets: List[Type['riscemu.InstructionSet']]):
|
|
|
|
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Creates a CPU instance.
|
|
|
|
Creates a CPU instance.
|
|
|
|
|
|
|
|
|
|
|
|
:param conf: An instance of the current RunConfiguration
|
|
|
|
|
|
|
|
:param instruction_sets: A list of instruction set classes. These must inherit from the InstructionSet class
|
|
|
|
:param instruction_sets: A list of instruction set classes. These must inherit from the InstructionSet class
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
# setup CPU states
|
|
|
|
# setup CPU states
|
|
|
|
self.pc = 0
|
|
|
|
super().__init__(MMU(), instruction_sets)
|
|
|
|
self.cycle = 0
|
|
|
|
|
|
|
|
self.exit: bool = False
|
|
|
|
|
|
|
|
self.exit_code: int = 0
|
|
|
|
|
|
|
|
self.conf = conf
|
|
|
|
|
|
|
|
self.active_debug = False # if a debugging session is currently runnign
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.stack: typing.Optional['riscemu.LoadedMemorySection'] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# setup MMU, registers and syscall handlers
|
|
|
|
|
|
|
|
self.mmu = MMU(conf)
|
|
|
|
|
|
|
|
self.regs = Registers(conf)
|
|
|
|
|
|
|
|
self.syscall_int = SyscallInterface()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# load all instruction sets
|
|
|
|
self.exit_code = 0
|
|
|
|
self.instruction_sets: List[riscemu.InstructionSet] = list()
|
|
|
|
|
|
|
|
self.instructions: Dict[str, Callable[[LoadedInstruction], None]] = dict()
|
|
|
|
|
|
|
|
for set_class in instruction_sets:
|
|
|
|
|
|
|
|
ins_set = set_class(self)
|
|
|
|
|
|
|
|
self.instructions.update(ins_set.load())
|
|
|
|
|
|
|
|
self.instruction_sets.append(ins_set)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# provide global syscall symbols if option is set
|
|
|
|
# setup syscall interface
|
|
|
|
if conf.include_scall_symbols:
|
|
|
|
self.syscall_int = SyscallInterface()
|
|
|
|
self.mmu.global_symbols.update(get_syscall_symbols())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def continue_from_debugger(self, verbose=True):
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
called from the debugger to continue running
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param verbose: If True, will print each executed instruction to STDOUT
|
|
|
|
# add global syscall symbols, but don't overwrite any user-defined symbols
|
|
|
|
"""
|
|
|
|
syscall_symbols = get_syscall_symbols()
|
|
|
|
self._run(verbose)
|
|
|
|
syscall_symbols.update(self.mmu.global_symbols)
|
|
|
|
|
|
|
|
self.mmu.global_symbols.update(syscall_symbols)
|
|
|
|
|
|
|
|
|
|
|
|
def step(self):
|
|
|
|
def step(self, verbose=False):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Execute a single instruction, then return.
|
|
|
|
Execute a single instruction, then return.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
if self.exit:
|
|
|
|
if self.halted:
|
|
|
|
print(FMT_CPU + "[CPU] Program exited with code {}".format(self.exit_code) + FMT_NONE)
|
|
|
|
print(FMT_CPU + "[CPU] Program exited with code {}".format(self.exit_code) + FMT_NONE)
|
|
|
|
else:
|
|
|
|
return
|
|
|
|
try:
|
|
|
|
|
|
|
|
self.cycle += 1
|
|
|
|
launch_debugger = False
|
|
|
|
ins = self.mmu.read_ins(self.pc)
|
|
|
|
|
|
|
|
print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins))
|
|
|
|
|
|
|
|
self.pc += self.INS_XLEN
|
|
|
|
|
|
|
|
self.run_instruction(ins)
|
|
|
|
|
|
|
|
except LaunchDebuggerException:
|
|
|
|
|
|
|
|
print(FMT_CPU + "[CPU] Returning to debugger!" + FMT_NONE)
|
|
|
|
|
|
|
|
except RiscemuBaseException as ex:
|
|
|
|
|
|
|
|
self.pc -= self.INS_XLEN
|
|
|
|
|
|
|
|
print(ex.message())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _run(self, verbose=False):
|
|
|
|
|
|
|
|
if self.pc <= 0:
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
ins = None
|
|
|
|
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
while not self.exit:
|
|
|
|
|
|
|
|
self.cycle += 1
|
|
|
|
self.cycle += 1
|
|
|
|
ins = self.mmu.read_ins(self.pc)
|
|
|
|
ins = self.mmu.read_ins(self.pc)
|
|
|
|
if verbose:
|
|
|
|
if verbose:
|
|
|
@ -110,33 +66,41 @@ class CPU:
|
|
|
|
self.pc += self.INS_XLEN
|
|
|
|
self.pc += self.INS_XLEN
|
|
|
|
self.run_instruction(ins)
|
|
|
|
self.run_instruction(ins)
|
|
|
|
except RiscemuBaseException as ex:
|
|
|
|
except RiscemuBaseException as ex:
|
|
|
|
if not isinstance(ex, LaunchDebuggerException):
|
|
|
|
if isinstance(ex, LaunchDebuggerException):
|
|
|
|
print(FMT_ERROR + "[CPU] excpetion caught at 0x{:08X}: {}:".format(self.pc - 1, ins) + FMT_NONE)
|
|
|
|
# if the debugger is active, raise the exception to
|
|
|
|
|
|
|
|
if self.debugger_active:
|
|
|
|
|
|
|
|
raise ex
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print(FMT_CPU + '[CPU] Debugger launch requested!' + FMT_NONE)
|
|
|
|
|
|
|
|
launch_debugger = True
|
|
|
|
|
|
|
|
else:
|
|
|
|
print(ex.message())
|
|
|
|
print(ex.message())
|
|
|
|
self.pc -= self.INS_XLEN
|
|
|
|
ex.print_stacktrace()
|
|
|
|
|
|
|
|
print(FMT_CPU + '[CPU] Halting due to exception!' + FMT_NONE)
|
|
|
|
|
|
|
|
self.halted = True
|
|
|
|
|
|
|
|
|
|
|
|
if self.active_debug:
|
|
|
|
if launch_debugger:
|
|
|
|
print(FMT_CPU + "[CPU] Returning to debugger!" + FMT_NONE)
|
|
|
|
launch_debug_session(self)
|
|
|
|
return
|
|
|
|
|
|
|
|
if self.conf.debug_on_exception:
|
|
|
|
|
|
|
|
launch_debug_session(self, self.mmu, self.regs, "Exception encountered, launching debug:")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if self.exit:
|
|
|
|
def run(self, verbose=False):
|
|
|
|
print()
|
|
|
|
while not self.halted:
|
|
|
|
print(FMT_CPU + "Program exited with code {}".format(self.exit_code) + FMT_NONE)
|
|
|
|
self.step(verbose)
|
|
|
|
sys.exit(self.exit_code)
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
print(FMT_CPU + "Program stopped without exiting - perhaps you stopped the debugger?" + FMT_NONE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
def setup_stack(self, stack_size=1024 * 4) -> bool:
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Returns a representation of the CPU and some of its state.
|
|
|
|
Create program stack and populate stack pointer
|
|
|
|
|
|
|
|
:param stack_size: the size of the required stack, defaults to 4Kib
|
|
|
|
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
return "{}(pc=0x{:08X}, cycle={}, exit={}, instructions={})".format(
|
|
|
|
stack_sec = BinaryDataMemorySection(
|
|
|
|
self.__class__.__name__,
|
|
|
|
bytearray(stack_size),
|
|
|
|
self.pc,
|
|
|
|
'.stack',
|
|
|
|
self.cycle,
|
|
|
|
None, # FIXME: why does a binary data memory section require a context?
|
|
|
|
self.exit,
|
|
|
|
'',
|
|
|
|
" ".join(s.name for s in self.instruction_sets)
|
|
|
|
0
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not self.mmu.load_section(stack_sec, fixed_position=False):
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.regs.set('sp', stack_sec.base + stack_sec.size)
|
|
|
|