user mode emulator finally working again

assembly-parser-rework
Anton Lydike 3 years ago
parent 5538034f8b
commit b396e0c5eb

@ -1,142 +1,106 @@
""" """
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.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 self.exit_code = 0
if conf.include_scall_symbols:
self.mmu.global_symbols.update(get_syscall_symbols())
def continue_from_debugger(self, verbose=True): # setup syscall interface
""" self.syscall_int = SyscallInterface()
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: print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins))
print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins)) 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)
if self.active_debug: self.halted = True
print(FMT_CPU + "[CPU] Returning to debugger!" + FMT_NONE)
return if launch_debugger:
if self.conf.debug_on_exception: launch_debug_session(self)
launch_debug_session(self, self.mmu, self.regs, "Exception encountered, launching debug:")
def run(self, verbose=False):
if self.exit: while not self.halted:
print() self.step(verbose)
print(FMT_CPU + "Program exited with code {}".format(self.exit_code) + FMT_NONE)
sys.exit(self.exit_code) def setup_stack(self, stack_size=1024 * 4) -> bool:
else:
print()
print(FMT_CPU + "Program stopped without exiting - perhaps you stopped the debugger?" + FMT_NONE)
def __repr__(self):
""" """
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)

@ -49,9 +49,11 @@ class MMU:
""" """
Create a new MMU Create a new MMU
""" """
self.programs = list()
self.sections = list() self.sections = list()
self.global_symbols = dict() self.global_symbols = dict()
def get_sec_containing(self, addr: T_AbsoluteAddress) -> Optional[MemorySection]: def get_sec_containing(self, addr: T_AbsoluteAddress) -> Optional[MemorySection]:
""" """
Returns the section that contains the address addr Returns the section that contains the address addr
@ -79,8 +81,8 @@ class MMU:
""" """
sec = self.get_sec_containing(addr) sec = self.get_sec_containing(addr)
if sec is None: if sec is None:
print(FMT_MEM + "[MMU] Trying to read instruction form invalid region! " print(FMT_MEM + "[MMU] Trying to read instruction form invalid region! (read at {}) ".format(addr)
"Have you forgotten an exit syscall or ret statement?" + FMT_NONE) + "Have you forgotten an exit syscall or ret statement?" + FMT_NONE)
raise RuntimeError("No next instruction available!") raise RuntimeError("No next instruction available!")
return sec.read_ins(addr - sec.base) return sec.read_ins(addr - sec.base)
@ -181,8 +183,7 @@ class MMU:
at_addr = program.base at_addr = program.base
else: else:
first_guaranteed_free_address = self.sections[-1].base + self.sections[-1].size at_addr = align_addr(self.get_guaranteed_free_address(), align_to)
at_addr = align_addr(first_guaranteed_free_address, align_to)
# trigger the load event to set all addresses in the binary # trigger the load event to set all addresses in the binary
program.loaded_trigger(at_addr) program.loaded_trigger(at_addr)
@ -200,10 +201,35 @@ class MMU:
# FIXME: this is pretty unclean and should probably be solved in a better way in the future # FIXME: this is pretty unclean and should probably be solved in a better way in the future
program.context.global_symbol_dict = self.global_symbols program.context.global_symbol_dict = self.global_symbols
def load_section(self, sec: MemorySection, fixed_position: bool = False) -> bool:
if fixed_position:
if self.has_continous_free_region(sec.base, sec.base + sec.size):
self.sections.append(sec)
self._update_state()
else:
print(FMT_MEM + '[MMU] Cannot place section {} at {}, space is occupied!'.format(sec, sec.base))
return False
else:
at_addr = align_addr(self.get_guaranteed_free_address(), 8)
sec.base = at_addr
self.sections.append(sec)
self._update_state()
return True
def _update_state(self): def _update_state(self):
"""
Called whenever a section or program is added to keep the list of programs and sections consistent
:return:
"""
self.programs.sort(key=lambda bin: bin.base) self.programs.sort(key=lambda bin: bin.base)
self.sections.sort(key=lambda sec: sec.base) self.sections.sort(key=lambda sec: sec.base)
def get_guaranteed_free_address(self) -> T_AbsoluteAddress:
if len(self.sections) == 0:
return 0x100
else:
return self.sections[-1].base + self.sections[-1].size
def __repr__(self): def __repr__(self):
return "MMU(\n\t{}\n)".format( return "MMU(\n\t{}\n)".format(
"\n\t".join(repr(x) for x in self.programs) "\n\t".join(repr(x) for x in self.programs)

@ -5,17 +5,18 @@ SPDX-License-Identifier: MIT
This file holds the logic for starting the emulator from the CLI This file holds the logic for starting the emulator from the CLI
""" """
from riscemu.CPU import UserModeCPU
if __name__ == '__main__': if __name__ == '__main__':
from . import * from .config import RunConfig
from .helpers import * from .helpers import *
from .instructions import InstructionSetDict from .instructions import InstructionSetDict
from riscemu.parser import AssemblyFileLoader
import argparse import argparse
import sys import sys
all_ins_names = list(InstructionSetDict.keys()) all_ins_names = list(InstructionSetDict.keys())
class OptionStringAction(argparse.Action): class OptionStringAction(argparse.Action):
def __init__(self, option_strings, dest, keys=None, omit_empty=False, **kwargs): def __init__(self, option_strings, dest, keys=None, omit_empty=False, **kwargs):
if keys is None: if keys is None:
@ -93,17 +94,21 @@ if __name__ == '__main__':
] ]
try: try:
cpu = CPU(cfg, ins_to_load) cpu = UserModeCPU(ins_to_load)
loaded_exe = None
opts = AssemblyFileLoader.get_options(sys.argv)
for file in args.files: for file in args.files:
tk = cpu.get_tokenizer(RiscVInput.from_file(file)) loader = AssemblyFileLoader.instantiate(file, opts)
tk.tokenize()
loaded_exe = cpu.load(ExecutableParser(tk).parse()) cpu.load_program(loader.parse())
# run the last loaded executable # run the last loaded executable
cpu.run_loaded(loaded_exe)
cpu.setup_stack(cfg.stack_size)
# launch the last loaded program
cpu.launch(cpu.mmu.programs[-1])
except RiscemuBaseException as e: except RiscemuBaseException as e:
print("Error while parsing: {}".format(e.message())) print("Error: {}".format(e.message()))
import traceback e.print_stacktrace()
traceback.print_exception(type(e), e, e.__traceback__)
sys.exit(1) sys.exit(1)

@ -1,11 +1,10 @@
""" """
RiscEmu (c) 2021 Anton Lydike RiscEmu (c) 2021-2022 Anton Lydike
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT
""" """
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional
@dataclass(frozen=True, init=True) @dataclass(frozen=True, init=True)

@ -4,35 +4,33 @@ RiscEmu (c) 2021 Anton Lydike
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT
""" """
import typing from .base import SimpleInstruction
from .registers import Registers
from .colors import FMT_DEBUG, FMT_NONE
from .types import Instruction
from .helpers import * from .helpers import *
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from . import * from riscemu import CPU, Registers
def launch_debug_session(cpu: 'CPU', mmu: 'MMU', reg: 'Registers', prompt=""): def launch_debug_session(cpu: 'CPU', prompt=""):
if not cpu.conf.debug_instruction or cpu.active_debug: if cpu.debugger_active:
return return
import code import code
import readline import readline
import rlcompleter import rlcompleter
cpu.active_debug = True # set the active debug flag
cpu.debugger_active = True
# setup some aliases: # setup some aliases:
registers = reg registers = cpu.regs
regs = reg regs = cpu.regs
memory = mmu memory = cpu.mmu
mem = mmu mem = cpu.mmu
syscall_interface = cpu.syscall_int mmu = cpu.mmu
# setup helper functions: # setup helper functions:
def dump(what, *args, **kwargs): def dump(what, *args, **kwargs):
if isinstance(what, Registers): if what == regs:
regs.dump(*args, **kwargs) regs.dump(*args, **kwargs)
else: else:
mmu.dump(what, *args, **kwargs) mmu.dump(what, *args, **kwargs)
@ -50,20 +48,39 @@ def launch_debug_session(cpu: 'CPU', mmu: 'MMU', reg: 'Registers', prompt=""):
return return
bin = mmu.get_bin_containing(cpu.pc) bin = mmu.get_bin_containing(cpu.pc)
ins = Instruction(name, list(args), bin) ins = SimpleInstruction(
print(FMT_DEBUG + "Running instruction " + ins + FMT_NONE) name,
tuple(args),
bin.context,
cpu.pc)
print(FMT_DEBUG + "Running instruction {}".format(ins) + FMT_NONE)
cpu.run_instruction(ins) cpu.run_instruction(ins)
def cont(verbose=False): def cont(verbose=False):
cpu.continue_from_debugger(verbose) try:
cpu.run(verbose)
except LaunchDebuggerException:
print(FMT_DEBUG + 'Returning to debugger...')
return
def step(): def step():
cpu.step() try:
cpu.step()
except LaunchDebuggerException:
return
# collect all variables
sess_vars = globals() sess_vars = globals()
sess_vars.update(locals()) sess_vars.update(locals())
# add tab completion
readline.set_completer(rlcompleter.Completer(sess_vars).complete) readline.set_completer(rlcompleter.Completer(sess_vars).complete)
readline.parse_and_bind("tab: complete") readline.parse_and_bind("tab: complete")
code.InteractiveConsole(sess_vars).interact(banner=FMT_DEBUG + prompt + FMT_NONE, exitmsg="Exiting debugger")
cpu.active_debug = False relaunch_debugger = False
try:
code.InteractiveConsole(sess_vars).interact(banner=FMT_DEBUG + prompt + FMT_NONE, exitmsg="Exiting debugger")
finally:
cpu.debugger_active = False

@ -17,6 +17,9 @@ class RiscemuBaseException(BaseException):
def message(self): def message(self):
pass pass
def print_stacktrace(self):
import traceback
traceback.print_exception(type(self), self, self.__traceback__)
# Parsing exceptions: # Parsing exceptions:
@ -115,13 +118,15 @@ class InvalidAllocationException(RiscemuBaseException):
class UnimplementedInstruction(RiscemuBaseException): class UnimplementedInstruction(RiscemuBaseException):
def __init__(self, ins: 'Instruction'): def __init__(self, ins: 'Instruction', context = None):
self.ins = ins self.ins = ins
self.context = context
def message(self): def message(self):
return FMT_CPU + "{}({})".format( return FMT_CPU + "{}({}{})".format(
self.__class__.__name__, self.__class__.__name__,
repr(self.ins) repr(self.ins),
', context={}'.format(self.context) if self.context is not None else ''
) + FMT_NONE ) + FMT_NONE

@ -1,4 +1,4 @@
from .InstructionSet import InstructionSet, Instruction from .instruction_set import InstructionSet, Instruction
from ..exceptions import INS_NOT_IMPLEMENTED from ..exceptions import INS_NOT_IMPLEMENTED
from ..helpers import int_from_bytes, int_to_bytes, to_unsigned, to_signed from ..helpers import int_from_bytes, int_to_bytes, to_unsigned, to_signed

@ -4,7 +4,8 @@ RiscEmu (c) 2021 Anton Lydike
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT
""" """
from .InstructionSet import * from .instruction_set import *
from ..CPU import UserModeCPU
from ..helpers import int_from_bytes, int_to_bytes, to_unsigned, to_signed from ..helpers import int_from_bytes, int_to_bytes, to_unsigned, to_signed
from ..colors import FMT_DEBUG, FMT_NONE from ..colors import FMT_DEBUG, FMT_NONE
@ -116,14 +117,8 @@ class RV32I(InstructionSet):
) )
def instruction_add(self, ins: 'Instruction'): def instruction_add(self, ins: 'Instruction'):
dst = "" # FIXME: once configuration is figured out, add flag to support immediate arg in add instruction
if self.cpu.conf.add_accept_imm: dst, rs1, rs2 = self.parse_rd_rs_rs(ins)
try:
dst, rs1, rs2 = self.parse_rd_rs_imm(ins)
except:
pass
if not dst:
dst, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set( self.regs.set(
dst, dst,
@ -292,20 +287,19 @@ class RV32I(InstructionSet):
def instruction_scall(self, ins: 'Instruction'): def instruction_scall(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 0) ASSERT_LEN(ins.args, 0)
if not isinstance(self.cpu, UserModeCPU):
# FIXME: add exception for syscall not supported or something
raise
syscall = Syscall(self.regs.get('a7'), self.cpu) syscall = Syscall(self.regs.get('a7'), self.cpu)
self.cpu.syscall_int.handle_syscall(syscall) self.cpu.syscall_int.handle_syscall(syscall)
def instruction_sbreak(self, ins: 'Instruction'): def instruction_sbreak(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 0) ASSERT_LEN(ins.args, 0)
if self.cpu.active_debug:
print(FMT_DEBUG + "Debug instruction encountered at 0x{:08X}".format(self.pc - 1) + FMT_NONE) print(FMT_DEBUG + "Debug instruction encountered at 0x{:08X}".format(self.pc - 1) + FMT_NONE)
raise LaunchDebuggerException() raise LaunchDebuggerException()
launch_debug_session(
self.cpu,
self.mmu,
self.regs,
"Debug instruction encountered at 0x{:08X}".format(self.pc - 1)
)
def instruction_nop(self, ins: 'Instruction'): def instruction_nop(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 0) ASSERT_LEN(ins.args, 0)

@ -4,7 +4,7 @@ RiscEmu (c) 2021 Anton Lydike
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT
""" """
from .InstructionSet import * from .instruction_set import *
from ..exceptions import INS_NOT_IMPLEMENTED from ..exceptions import INS_NOT_IMPLEMENTED

@ -6,7 +6,7 @@ SPDX-License-Identifier: MIT
This package holds all instruction sets, available to the processor This package holds all instruction sets, available to the processor
""" """
from .InstructionSet import InstructionSet from .instruction_set import InstructionSet
from .RV32M import RV32M from .RV32M import RV32M
from .RV32I import RV32I from .RV32I import RV32I
from .RV32A import RV32A from .RV32A import RV32A

@ -53,6 +53,7 @@ def parse_tokens(name: str, tokens_iter: Iterable[Token]) -> Program:
for token, args in composite_tokenizer(Peekable[Token](tokens_iter)): for token, args in composite_tokenizer(Peekable[Token](tokens_iter)):
if token.type not in PARSERS: if token.type not in PARSERS:
raise ParseException("Unexpected token type: {}, {}".format(token, args)) raise ParseException("Unexpected token type: {}, {}".format(token, args))
print('{}: {}'.format(token, args))
PARSERS[token.type](token, args, context) PARSERS[token.type](token, args, context)
return context.finalize() return context.finalize()
@ -73,6 +74,8 @@ def composite_tokenizer(tokens_iter: Iterable[Token]) -> Iterable[Tuple[Token, T
token = next(tokens) token = next(tokens)
if token.type in (TokenType.PSEUDO_OP, TokenType.LABEL, TokenType.INSTRUCTION_NAME): if token.type in (TokenType.PSEUDO_OP, TokenType.LABEL, TokenType.INSTRUCTION_NAME):
yield token, tuple(take_arguments(tokens)) yield token, tuple(take_arguments(tokens))
else:
print("skipped {}".format(token))
def take_arguments(tokens: Peekable[Token]) -> Iterable[str]: def take_arguments(tokens: Peekable[Token]) -> Iterable[str]:
@ -85,12 +88,14 @@ def take_arguments(tokens: Peekable[Token]) -> Iterable[str]:
while True: while True:
if tokens.peek().type == TokenType.ARGUMENT: if tokens.peek().type == TokenType.ARGUMENT:
yield next(tokens).value yield next(tokens).value
if tokens.peek().type == TokenType.COMMA:
next(tokens)
elif tokens.peek().type == TokenType.NEWLINE: elif tokens.peek().type == TokenType.NEWLINE:
next(tokens) next(tokens)
break break
break elif tokens.peek().type == TokenType.COMMA:
next(tokens)
else:
break
# raise ParseException("Expected newline, instead got {}".format(tokens.peek())) # raise ParseException("Expected newline, instead got {}".format(tokens.peek()))

@ -16,7 +16,7 @@ from ..instructions import RV32A, RV32M
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from riscemu import types, LoadedExecutable, LoadedInstruction from riscemu import types, LoadedExecutable, LoadedInstruction
from riscemu.instructions.InstructionSet import InstructionSet from riscemu.instructions.instruction_set import InstructionSet
class PrivCPU(CPU): class PrivCPU(CPU):
@ -83,7 +83,7 @@ class PrivCPU(CPU):
self.launch_debug = False self.launch_debug = False
launch_debug_session(self, self.mmu, self.regs, launch_debug_session(self, self.mmu, self.regs,
"Launching debugger:") "Launching debugger:")
if not self.active_debug: if not self.debugger_active:
self._run(verbose) self._run(verbose)
else: else:
print() print()

@ -1,28 +1,23 @@
""" """
RiscEmu (c) 2021 Anton Lydike RiscEmu (c) 2021-2022 Anton Lydike
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT
""" """
from .config import RunConfig
from .helpers import *
from collections import defaultdict from collections import defaultdict
from .exceptions import InvalidRegisterException
from .helpers import *
class Registers: class Registers:
""" """
Represents a bunch of registers Represents a bunch of registers
""" """
def __init__(self, conf: RunConfig): def __init__(self):
"""
Initialize the register configuration, respecting the RunConfig conf
:param conf: The RunConfig
"""
self.vals = defaultdict(lambda: 0) self.vals = defaultdict(lambda: 0)
self.last_set = None self.last_set = None
self.last_read = None self.last_read = None
self.conf = conf
def dump(self, full=False): def dump(self, full=False):
""" """
@ -96,7 +91,7 @@ class Registers:
""" """
if reg == 'zero': if reg == 'zero':
return False return False
#if reg not in Registers.all_registers(): # if reg not in Registers.all_registers():
# raise InvalidRegisterException(reg) # raise InvalidRegisterException(reg)
# replace fp register with s1, as these are the same register # replace fp register with s1, as these are the same register
if reg == 'fp': if reg == 'fp':
@ -114,7 +109,7 @@ class Registers:
:param mark_read: If the register should be markes as "last read" (only used internally) :param mark_read: If the register should be markes as "last read" (only used internally)
:return: The contents of register reg :return: The contents of register reg
""" """
#if reg not in Registers.all_registers(): # if reg not in Registers.all_registers():
# raise InvalidRegisterException(reg) # raise InvalidRegisterException(reg)
if reg == 'fp': if reg == 'fp':
reg = 's0' reg = 's0'

@ -15,7 +15,7 @@ import riscemu
import typing import typing
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from . import CPU from riscemu.CPU import UserModeCPU
SYSCALLS = { SYSCALLS = {
63: 'read', 63: 'read',
@ -43,7 +43,7 @@ class Syscall:
""" """
id: int id: int
"""The syscall number (e.g. 64 - write)""" """The syscall number (e.g. 64 - write)"""
cpu: 'riscemu.CPU' cpu: 'UserModeCPU'
"""The CPU object that created the syscall""" """The CPU object that created the syscall"""
@property @property
@ -146,7 +146,8 @@ class SyscallInterface:
Requires running with flag scall-fs Requires running with flag scall-fs
""" """
if not scall.cpu.conf.scall_fs: # FIXME: this should be toggleable in a global setting or somethign
if True:
print(FMT_SYSCALL + '[Syscall] open: opening files not supported without scall-fs flag!' + FMT_NONE) print(FMT_SYSCALL + '[Syscall] open: opening files not supported without scall-fs flag!' + FMT_NONE)
return scall.ret(-1) return scall.ret(-1)
@ -193,7 +194,7 @@ class SyscallInterface:
""" """
Exit syscall. Exits the system with status code a0 Exit syscall. Exits the system with status code a0
""" """
scall.cpu.exit = True scall.cpu.halted = True
scall.cpu.exit_code = scall.cpu.regs.get('a0') scall.cpu.exit_code = scall.cpu.regs.get('a0')
def __repr__(self): def __repr__(self):

@ -14,7 +14,7 @@ from .exceptions import ParseException
LINE_COMMENT_STARTERS = ('#', ';', '//') LINE_COMMENT_STARTERS = ('#', ';', '//')
WHITESPACE_PATTERN = re.compile(r'\s+') WHITESPACE_PATTERN = re.compile(r'\s+')
MEMORY_ADDRESS_PATTERN = re.compile(r'^(0[xX][A-f0-9]+|\d+|0b[0-1]+)\(([A-z]+[0-9]{0,2})\)$') MEMORY_ADDRESS_PATTERN = re.compile(r'^(0[xX][A-f0-9]+|\d+|0b[0-1]+|[A-z0-9_-]+)\(([A-z]+[0-9]{0,2})\)$')
REGISTER_NAMES = RISCV_REGS REGISTER_NAMES = RISCV_REGS

@ -9,16 +9,20 @@ See base.py for some basic implementations of these classes
""" """
import os import os
import re import re
import typing
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from collections import defaultdict from collections import defaultdict
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple, Set, Union, Generator, Iterator, Callable, Type from typing import Dict, List, Optional, Tuple, Set, Union, Iterator, Callable, Type
from . import MMU, InstructionSet from .colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE, FMT_RED, FMT_BOLD
from .assembler import get_section_base_name
from .colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE, FMT_PARSE, FMT_RED, FMT_BOLD
from .exceptions import ParseException from .exceptions import ParseException
from .helpers import format_bytes from .helpers import format_bytes, get_section_base_name
from .registers import Registers
if typing.TYPE_CHECKING:
from .MMU import MMU
from .instructions.instruction_set import InstructionSet
# define some base type aliases so we can keep track of absolute and relative addresses # define some base type aliases so we can keep track of absolute and relative addresses
T_RelativeAddress = int T_RelativeAddress = int
@ -231,7 +235,7 @@ class Program:
self.sections = [] self.sections = []
self.global_labels = set() self.global_labels = set()
self.base = base self.base = base
self.loaded = False self.is_loaded = False
def add_section(self, sec: MemorySection): def add_section(self, sec: MemorySection):
# print a warning when a section is located before the programs base # print a warning when a section is located before the programs base
@ -286,7 +290,7 @@ class Program:
for sec in self.sections: for sec in self.sections:
sec.base += at_addr sec.base += at_addr
if self.base != at_addr: if self.base is not None and self.base != at_addr:
# move sections so they are located where they want to be located # move sections so they are located where they want to be located
offset = at_addr - self.base offset = at_addr - self.base
for sec in self.sections: for sec in self.sections:
@ -354,7 +358,8 @@ class CPU(ABC):
INS_XLEN: int = 4 INS_XLEN: int = 4
# housekeeping variables # housekeeping variables
mmu: MMU regs: Registers
mmu: 'MMU'
pc: T_AbsoluteAddress pc: T_AbsoluteAddress
cycle: int cycle: int
halted: bool halted: bool
@ -364,10 +369,11 @@ class CPU(ABC):
# instruction information # instruction information
instructions: Dict[str, Callable[[Instruction], None]] instructions: Dict[str, Callable[[Instruction], None]]
instruction_sets: Set[InstructionSet] instruction_sets: Set['InstructionSet']
def __init__(self, mmu: MMU, instruction_sets: List[Type[InstructionSet]]): def __init__(self, mmu: 'MMU', instruction_sets: List[Type['InstructionSet']]):
self.mmu = mmu self.mmu = mmu
self.regs = Registers()
self.instruction_sets = set() self.instruction_sets = set()
self.instructions = dict() self.instructions = dict()
@ -377,6 +383,7 @@ class CPU(ABC):
self.instructions.update(ins_set.load()) self.instructions.update(ins_set.load())
self.instruction_sets.add(ins_set) self.instruction_sets.add(ins_set)
self.halted = False
self.cycle = 0 self.cycle = 0
self.pc = 0 self.pc = 0
self.debugger_active = False self.debugger_active = False
@ -410,3 +417,19 @@ class CPU(ABC):
self.halted, self.halted,
" ".join(s.name for s in self.instruction_sets) " ".join(s.name for s in self.instruction_sets)
) )
@abstractmethod
def step(self, verbose=False):
pass
@abstractmethod
def run(self, verbose=False):
pass
def launch(self, program: Program, verbose: bool = False):
if program not in self.mmu.programs:
print(FMT_RED + '[CPU] Cannot launch program that\'s not loaded!' + FMT_NONE)
return
self.pc = program.entrypoint
self.run(verbose)

Loading…
Cancel
Save