user mode emulator finally working again
This commit is contained in:
parent
5538034f8b
commit
b396e0c5eb
162
riscemu/CPU.py
162
riscemu/CPU.py
@ -1,142 +1,106 @@
|
||||
"""
|
||||
RiscEmu (c) 2021 Anton Lydike
|
||||
RiscEmu (c) 2021-2022 Anton Lydike
|
||||
|
||||
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.
|
||||
"""
|
||||
import sys
|
||||
from typing import Tuple, List, Dict, Callable, Type
|
||||
|
||||
from .types import MemoryFlags
|
||||
from .syscall import SyscallInterface, get_syscall_symbols
|
||||
from .exceptions import RiscemuBaseException, LaunchDebuggerException
|
||||
from .MMU import MMU
|
||||
from .config import RunConfig
|
||||
from .registers import Registers
|
||||
from .debug import launch_debug_session
|
||||
from .colors import FMT_CPU, FMT_NONE, FMT_ERROR
|
||||
import typing
|
||||
from typing import List, Type
|
||||
|
||||
import riscemu
|
||||
|
||||
import typing
|
||||
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
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import types, LoadedExecutable, LoadedInstruction
|
||||
from .instructions.InstructionSet import InstructionSet
|
||||
from .instructions.instruction_set 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.
|
||||
|
||||
It is initialized with a configuration and a list of instruction sets.
|
||||
"""
|
||||
|
||||
INS_XLEN = 4
|
||||
|
||||
def __init__(self, conf: RunConfig, instruction_sets: List[Type['riscemu.InstructionSet']]):
|
||||
def __init__(self, instruction_sets: List[Type['riscemu.InstructionSet']]):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
# setup CPU states
|
||||
self.pc = 0
|
||||
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
|
||||
super().__init__(MMU(), instruction_sets)
|
||||
|
||||
self.stack: typing.Optional['riscemu.LoadedMemorySection'] = None
|
||||
self.exit_code = 0
|
||||
|
||||
# setup MMU, registers and syscall handlers
|
||||
self.mmu = MMU(conf)
|
||||
self.regs = Registers(conf)
|
||||
# setup syscall interface
|
||||
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)
|
||||
# add global syscall symbols, but don't overwrite any user-defined symbols
|
||||
syscall_symbols = get_syscall_symbols()
|
||||
syscall_symbols.update(self.mmu.global_symbols)
|
||||
self.mmu.global_symbols.update(syscall_symbols)
|
||||
|
||||
# provide global syscall symbols if option is set
|
||||
if conf.include_scall_symbols:
|
||||
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
|
||||
"""
|
||||
self._run(verbose)
|
||||
|
||||
def step(self):
|
||||
def step(self, verbose=False):
|
||||
"""
|
||||
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)
|
||||
else:
|
||||
try:
|
||||
self.cycle += 1
|
||||
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())
|
||||
return
|
||||
|
||||
launch_debugger = False
|
||||
|
||||
def _run(self, verbose=False):
|
||||
if self.pc <= 0:
|
||||
return False
|
||||
ins = None
|
||||
try:
|
||||
while not self.exit:
|
||||
self.cycle += 1
|
||||
ins = self.mmu.read_ins(self.pc)
|
||||
if verbose:
|
||||
print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins))
|
||||
self.pc += self.INS_XLEN
|
||||
self.run_instruction(ins)
|
||||
self.cycle += 1
|
||||
ins = self.mmu.read_ins(self.pc)
|
||||
if verbose:
|
||||
print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins))
|
||||
self.pc += self.INS_XLEN
|
||||
self.run_instruction(ins)
|
||||
except RiscemuBaseException as ex:
|
||||
if not isinstance(ex, LaunchDebuggerException):
|
||||
print(FMT_ERROR + "[CPU] excpetion caught at 0x{:08X}: {}:".format(self.pc - 1, ins) + FMT_NONE)
|
||||
if isinstance(ex, LaunchDebuggerException):
|
||||
# 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())
|
||||
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:
|
||||
print(FMT_CPU + "[CPU] Returning to debugger!" + FMT_NONE)
|
||||
return
|
||||
if self.conf.debug_on_exception:
|
||||
launch_debug_session(self, self.mmu, self.regs, "Exception encountered, launching debug:")
|
||||
if launch_debugger:
|
||||
launch_debug_session(self)
|
||||
|
||||
if self.exit:
|
||||
print()
|
||||
print(FMT_CPU + "Program exited with code {}".format(self.exit_code) + FMT_NONE)
|
||||
sys.exit(self.exit_code)
|
||||
else:
|
||||
print()
|
||||
print(FMT_CPU + "Program stopped without exiting - perhaps you stopped the debugger?" + FMT_NONE)
|
||||
def run(self, verbose=False):
|
||||
while not self.halted:
|
||||
self.step(verbose)
|
||||
|
||||
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(
|
||||
self.__class__.__name__,
|
||||
self.pc,
|
||||
self.cycle,
|
||||
self.exit,
|
||||
" ".join(s.name for s in self.instruction_sets)
|
||||
stack_sec = BinaryDataMemorySection(
|
||||
bytearray(stack_size),
|
||||
'.stack',
|
||||
None, # FIXME: why does a binary data memory section require a context?
|
||||
'',
|
||||
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
|
||||
"""
|
||||
self.programs = list()
|
||||
self.sections = list()
|
||||
self.global_symbols = dict()
|
||||
|
||||
|
||||
def get_sec_containing(self, addr: T_AbsoluteAddress) -> Optional[MemorySection]:
|
||||
"""
|
||||
Returns the section that contains the address addr
|
||||
@ -79,8 +81,8 @@ class MMU:
|
||||
"""
|
||||
sec = self.get_sec_containing(addr)
|
||||
if sec is None:
|
||||
print(FMT_MEM + "[MMU] Trying to read instruction form invalid region! "
|
||||
"Have you forgotten an exit syscall or ret statement?" + FMT_NONE)
|
||||
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)
|
||||
raise RuntimeError("No next instruction available!")
|
||||
return sec.read_ins(addr - sec.base)
|
||||
|
||||
@ -181,8 +183,7 @@ class MMU:
|
||||
|
||||
at_addr = program.base
|
||||
else:
|
||||
first_guaranteed_free_address = self.sections[-1].base + self.sections[-1].size
|
||||
at_addr = align_addr(first_guaranteed_free_address, align_to)
|
||||
at_addr = align_addr(self.get_guaranteed_free_address(), align_to)
|
||||
|
||||
# trigger the load event to set all addresses in the binary
|
||||
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
|
||||
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):
|
||||
"""
|
||||
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.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):
|
||||
return "MMU(\n\t{}\n)".format(
|
||||
"\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
|
||||
"""
|
||||
from riscemu.CPU import UserModeCPU
|
||||
|
||||
if __name__ == '__main__':
|
||||
from . import *
|
||||
from .config import RunConfig
|
||||
from .helpers import *
|
||||
from .instructions import InstructionSetDict
|
||||
from riscemu.parser import AssemblyFileLoader
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
all_ins_names = list(InstructionSetDict.keys())
|
||||
|
||||
|
||||
class OptionStringAction(argparse.Action):
|
||||
def __init__(self, option_strings, dest, keys=None, omit_empty=False, **kwargs):
|
||||
if keys is None:
|
||||
@ -93,17 +94,21 @@ if __name__ == '__main__':
|
||||
]
|
||||
|
||||
try:
|
||||
cpu = CPU(cfg, ins_to_load)
|
||||
loaded_exe = None
|
||||
for file in args.files:
|
||||
tk = cpu.get_tokenizer(RiscVInput.from_file(file))
|
||||
tk.tokenize()
|
||||
loaded_exe = cpu.load(ExecutableParser(tk).parse())
|
||||
# run the last loaded executable
|
||||
cpu.run_loaded(loaded_exe)
|
||||
except RiscemuBaseException as e:
|
||||
print("Error while parsing: {}".format(e.message()))
|
||||
import traceback
|
||||
cpu = UserModeCPU(ins_to_load)
|
||||
|
||||
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
|
||||
|
||||
cpu.setup_stack(cfg.stack_size)
|
||||
|
||||
# launch the last loaded program
|
||||
cpu.launch(cpu.mmu.programs[-1])
|
||||
except RiscemuBaseException as e:
|
||||
print("Error: {}".format(e.message()))
|
||||
e.print_stacktrace()
|
||||
|
||||
traceback.print_exception(type(e), e, e.__traceback__)
|
||||
sys.exit(1)
|
||||
|
@ -1,11 +1,10 @@
|
||||
"""
|
||||
RiscEmu (c) 2021 Anton Lydike
|
||||
RiscEmu (c) 2021-2022 Anton Lydike
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass(frozen=True, init=True)
|
||||
|
@ -4,35 +4,33 @@ RiscEmu (c) 2021 Anton Lydike
|
||||
SPDX-License-Identifier: MIT
|
||||
"""
|
||||
|
||||
import typing
|
||||
from .registers import Registers
|
||||
from .colors import FMT_DEBUG, FMT_NONE
|
||||
from .types import Instruction
|
||||
from .base import SimpleInstruction
|
||||
from .helpers import *
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import *
|
||||
from riscemu import CPU, Registers
|
||||
|
||||
|
||||
def launch_debug_session(cpu: 'CPU', mmu: 'MMU', reg: 'Registers', prompt=""):
|
||||
if not cpu.conf.debug_instruction or cpu.active_debug:
|
||||
def launch_debug_session(cpu: 'CPU', prompt=""):
|
||||
if cpu.debugger_active:
|
||||
return
|
||||
import code
|
||||
import readline
|
||||
import rlcompleter
|
||||
|
||||
cpu.active_debug = True
|
||||
# set the active debug flag
|
||||
cpu.debugger_active = True
|
||||
|
||||
# setup some aliases:
|
||||
registers = reg
|
||||
regs = reg
|
||||
memory = mmu
|
||||
mem = mmu
|
||||
syscall_interface = cpu.syscall_int
|
||||
registers = cpu.regs
|
||||
regs = cpu.regs
|
||||
memory = cpu.mmu
|
||||
mem = cpu.mmu
|
||||
mmu = cpu.mmu
|
||||
|
||||
# setup helper functions:
|
||||
def dump(what, *args, **kwargs):
|
||||
if isinstance(what, Registers):
|
||||
if what == regs:
|
||||
regs.dump(*args, **kwargs)
|
||||
else:
|
||||
mmu.dump(what, *args, **kwargs)
|
||||
@ -50,20 +48,39 @@ def launch_debug_session(cpu: 'CPU', mmu: 'MMU', reg: 'Registers', prompt=""):
|
||||
return
|
||||
bin = mmu.get_bin_containing(cpu.pc)
|
||||
|
||||
ins = Instruction(name, list(args), bin)
|
||||
print(FMT_DEBUG + "Running instruction " + ins + FMT_NONE)
|
||||
ins = SimpleInstruction(
|
||||
name,
|
||||
tuple(args),
|
||||
bin.context,
|
||||
cpu.pc)
|
||||
print(FMT_DEBUG + "Running instruction {}".format(ins) + FMT_NONE)
|
||||
cpu.run_instruction(ins)
|
||||
|
||||
def cont(verbose=False):
|
||||
cpu.continue_from_debugger(verbose)
|
||||
try:
|
||||
cpu.run(verbose)
|
||||
except LaunchDebuggerException:
|
||||
print(FMT_DEBUG + 'Returning to debugger...')
|
||||
return
|
||||
|
||||
def step():
|
||||
cpu.step()
|
||||
try:
|
||||
cpu.step()
|
||||
except LaunchDebuggerException:
|
||||
return
|
||||
|
||||
# collect all variables
|
||||
sess_vars = globals()
|
||||
sess_vars.update(locals())
|
||||
|
||||
# add tab completion
|
||||
readline.set_completer(rlcompleter.Completer(sess_vars).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):
|
||||
pass
|
||||
|
||||
def print_stacktrace(self):
|
||||
import traceback
|
||||
traceback.print_exception(type(self), self, self.__traceback__)
|
||||
|
||||
# Parsing exceptions:
|
||||
|
||||
@ -115,13 +118,15 @@ class InvalidAllocationException(RiscemuBaseException):
|
||||
|
||||
|
||||
class UnimplementedInstruction(RiscemuBaseException):
|
||||
def __init__(self, ins: 'Instruction'):
|
||||
def __init__(self, ins: 'Instruction', context = None):
|
||||
self.ins = ins
|
||||
self.context = context
|
||||
|
||||
def message(self):
|
||||
return FMT_CPU + "{}({})".format(
|
||||
return FMT_CPU + "{}({}{})".format(
|
||||
self.__class__.__name__,
|
||||
repr(self.ins)
|
||||
repr(self.ins),
|
||||
', context={}'.format(self.context) if self.context is not None else ''
|
||||
) + FMT_NONE
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
from .InstructionSet import InstructionSet, Instruction
|
||||
from .instruction_set import InstructionSet, Instruction
|
||||
from ..exceptions import INS_NOT_IMPLEMENTED
|
||||
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
|
||||
"""
|
||||
|
||||
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 ..colors import FMT_DEBUG, FMT_NONE
|
||||
@ -116,14 +117,8 @@ class RV32I(InstructionSet):
|
||||
)
|
||||
|
||||
def instruction_add(self, ins: 'Instruction'):
|
||||
dst = ""
|
||||
if self.cpu.conf.add_accept_imm:
|
||||
try:
|
||||
dst, rs1, rs2 = self.parse_rd_rs_imm(ins)
|
||||
except:
|
||||
pass
|
||||
if not dst:
|
||||
dst, rs1, rs2 = self.parse_rd_rs_rs(ins)
|
||||
# FIXME: once configuration is figured out, add flag to support immediate arg in add instruction
|
||||
dst, rs1, rs2 = self.parse_rd_rs_rs(ins)
|
||||
|
||||
self.regs.set(
|
||||
dst,
|
||||
@ -292,20 +287,19 @@ class RV32I(InstructionSet):
|
||||
|
||||
def instruction_scall(self, ins: 'Instruction'):
|
||||
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)
|
||||
self.cpu.syscall_int.handle_syscall(syscall)
|
||||
|
||||
def instruction_sbreak(self, ins: 'Instruction'):
|
||||
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)
|
||||
raise LaunchDebuggerException()
|
||||
launch_debug_session(
|
||||
self.cpu,
|
||||
self.mmu,
|
||||
self.regs,
|
||||
"Debug instruction encountered at 0x{:08X}".format(self.pc - 1)
|
||||
)
|
||||
|
||||
print(FMT_DEBUG + "Debug instruction encountered at 0x{:08X}".format(self.pc - 1) + FMT_NONE)
|
||||
raise LaunchDebuggerException()
|
||||
|
||||
def instruction_nop(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 0)
|
||||
|
@ -4,7 +4,7 @@ RiscEmu (c) 2021 Anton Lydike
|
||||
SPDX-License-Identifier: MIT
|
||||
"""
|
||||
|
||||
from .InstructionSet import *
|
||||
from .instruction_set import *
|
||||
from ..exceptions import INS_NOT_IMPLEMENTED
|
||||
|
||||
|
||||
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: MIT
|
||||
This package holds all instruction sets, available to the processor
|
||||
"""
|
||||
|
||||
from .InstructionSet import InstructionSet
|
||||
from .instruction_set import InstructionSet
|
||||
from .RV32M import RV32M
|
||||
from .RV32I import RV32I
|
||||
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)):
|
||||
if token.type not in PARSERS:
|
||||
raise ParseException("Unexpected token type: {}, {}".format(token, args))
|
||||
print('{}: {}'.format(token, args))
|
||||
PARSERS[token.type](token, args, context)
|
||||
|
||||
return context.finalize()
|
||||
@ -73,6 +74,8 @@ def composite_tokenizer(tokens_iter: Iterable[Token]) -> Iterable[Tuple[Token, T
|
||||
token = next(tokens)
|
||||
if token.type in (TokenType.PSEUDO_OP, TokenType.LABEL, TokenType.INSTRUCTION_NAME):
|
||||
yield token, tuple(take_arguments(tokens))
|
||||
else:
|
||||
print("skipped {}".format(token))
|
||||
|
||||
|
||||
def take_arguments(tokens: Peekable[Token]) -> Iterable[str]:
|
||||
@ -85,12 +88,14 @@ def take_arguments(tokens: Peekable[Token]) -> Iterable[str]:
|
||||
while True:
|
||||
if tokens.peek().type == TokenType.ARGUMENT:
|
||||
yield next(tokens).value
|
||||
if tokens.peek().type == TokenType.COMMA:
|
||||
next(tokens)
|
||||
elif tokens.peek().type == TokenType.NEWLINE:
|
||||
next(tokens)
|
||||
break
|
||||
break
|
||||
elif tokens.peek().type == TokenType.COMMA:
|
||||
next(tokens)
|
||||
else:
|
||||
break
|
||||
|
||||
# raise ParseException("Expected newline, instead got {}".format(tokens.peek()))
|
||||
|
||||
|
||||
|
@ -16,7 +16,7 @@ from ..instructions import RV32A, RV32M
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from riscemu import types, LoadedExecutable, LoadedInstruction
|
||||
from riscemu.instructions.InstructionSet import InstructionSet
|
||||
from riscemu.instructions.instruction_set import InstructionSet
|
||||
|
||||
|
||||
class PrivCPU(CPU):
|
||||
@ -83,7 +83,7 @@ class PrivCPU(CPU):
|
||||
self.launch_debug = False
|
||||
launch_debug_session(self, self.mmu, self.regs,
|
||||
"Launching debugger:")
|
||||
if not self.active_debug:
|
||||
if not self.debugger_active:
|
||||
self._run(verbose)
|
||||
else:
|
||||
print()
|
||||
|
@ -1,28 +1,23 @@
|
||||
"""
|
||||
RiscEmu (c) 2021 Anton Lydike
|
||||
RiscEmu (c) 2021-2022 Anton Lydike
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
"""
|
||||
|
||||
from .config import RunConfig
|
||||
from .helpers import *
|
||||
from collections import defaultdict
|
||||
from .exceptions import InvalidRegisterException
|
||||
|
||||
from .helpers import *
|
||||
|
||||
|
||||
class Registers:
|
||||
"""
|
||||
Represents a bunch of registers
|
||||
"""
|
||||
|
||||
def __init__(self, conf: RunConfig):
|
||||
"""
|
||||
Initialize the register configuration, respecting the RunConfig conf
|
||||
:param conf: The RunConfig
|
||||
"""
|
||||
def __init__(self):
|
||||
self.vals = defaultdict(lambda: 0)
|
||||
self.last_set = None
|
||||
self.last_read = None
|
||||
self.conf = conf
|
||||
|
||||
def dump(self, full=False):
|
||||
"""
|
||||
@ -96,7 +91,7 @@ class Registers:
|
||||
"""
|
||||
if reg == 'zero':
|
||||
return False
|
||||
#if reg not in Registers.all_registers():
|
||||
# if reg not in Registers.all_registers():
|
||||
# raise InvalidRegisterException(reg)
|
||||
# replace fp register with s1, as these are the same register
|
||||
if reg == 'fp':
|
||||
@ -114,7 +109,7 @@ class Registers:
|
||||
:param mark_read: If the register should be markes as "last read" (only used internally)
|
||||
:return: The contents of register reg
|
||||
"""
|
||||
#if reg not in Registers.all_registers():
|
||||
# if reg not in Registers.all_registers():
|
||||
# raise InvalidRegisterException(reg)
|
||||
if reg == 'fp':
|
||||
reg = 's0'
|
||||
|
@ -15,7 +15,7 @@ import riscemu
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import CPU
|
||||
from riscemu.CPU import UserModeCPU
|
||||
|
||||
SYSCALLS = {
|
||||
63: 'read',
|
||||
@ -43,7 +43,7 @@ class Syscall:
|
||||
"""
|
||||
id: int
|
||||
"""The syscall number (e.g. 64 - write)"""
|
||||
cpu: 'riscemu.CPU'
|
||||
cpu: 'UserModeCPU'
|
||||
"""The CPU object that created the syscall"""
|
||||
|
||||
@property
|
||||
@ -146,7 +146,8 @@ class SyscallInterface:
|
||||
|
||||
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)
|
||||
return scall.ret(-1)
|
||||
|
||||
@ -193,7 +194,7 @@ class SyscallInterface:
|
||||
"""
|
||||
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')
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -14,7 +14,7 @@ from .exceptions import ParseException
|
||||
|
||||
LINE_COMMENT_STARTERS = ('#', ';', '//')
|
||||
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
|
||||
|
||||
|
||||
|
@ -9,16 +9,20 @@ See base.py for some basic implementations of these classes
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import typing
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import defaultdict
|
||||
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 .assembler import get_section_base_name
|
||||
from .colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE, FMT_PARSE, FMT_RED, FMT_BOLD
|
||||
from .colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE, FMT_RED, FMT_BOLD
|
||||
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
|
||||
T_RelativeAddress = int
|
||||
@ -231,7 +235,7 @@ class Program:
|
||||
self.sections = []
|
||||
self.global_labels = set()
|
||||
self.base = base
|
||||
self.loaded = False
|
||||
self.is_loaded = False
|
||||
|
||||
def add_section(self, sec: MemorySection):
|
||||
# print a warning when a section is located before the programs base
|
||||
@ -286,7 +290,7 @@ class Program:
|
||||
for sec in self.sections:
|
||||
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
|
||||
offset = at_addr - self.base
|
||||
for sec in self.sections:
|
||||
@ -354,7 +358,8 @@ class CPU(ABC):
|
||||
INS_XLEN: int = 4
|
||||
|
||||
# housekeeping variables
|
||||
mmu: MMU
|
||||
regs: Registers
|
||||
mmu: 'MMU'
|
||||
pc: T_AbsoluteAddress
|
||||
cycle: int
|
||||
halted: bool
|
||||
@ -364,10 +369,11 @@ class CPU(ABC):
|
||||
|
||||
# instruction information
|
||||
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.regs = Registers()
|
||||
|
||||
self.instruction_sets = set()
|
||||
self.instructions = dict()
|
||||
@ -377,6 +383,7 @@ class CPU(ABC):
|
||||
self.instructions.update(ins_set.load())
|
||||
self.instruction_sets.add(ins_set)
|
||||
|
||||
self.halted = False
|
||||
self.cycle = 0
|
||||
self.pc = 0
|
||||
self.debugger_active = False
|
||||
@ -410,3 +417,19 @@ class CPU(ABC):
|
||||
self.halted,
|
||||
" ".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…
Reference in New Issue
Block a user