diff --git a/riscemu/CPU.py b/riscemu/CPU.py index 2b72a66..2ac4548 100644 --- a/riscemu/CPU.py +++ b/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 +import typing +from typing import List, Type -from .types import MemoryFlags -from .syscall import SyscallInterface, get_syscall_symbols -from .exceptions import RiscemuBaseException, LaunchDebuggerException +import riscemu from .MMU import MMU -from .config import RunConfig -from .registers import Registers +from .base import BinaryDataMemorySection +from .colors import FMT_CPU, FMT_NONE from .debug import launch_debug_session -from .colors import FMT_CPU, FMT_NONE, FMT_ERROR - -import riscemu - -import typing +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 - - 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) + super().__init__(MMU(), instruction_sets) - # provide global syscall symbols if option is set - if conf.include_scall_symbols: - self.mmu.global_symbols.update(get_syscall_symbols()) + self.exit_code = 0 - def continue_from_debugger(self, verbose=True): - """ - called from the debugger to continue running + # setup syscall interface + self.syscall_int = SyscallInterface() - :param verbose: If True, will print each executed instruction to STDOUT - """ - self._run(verbose) + # 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) - 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 - - 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 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 __repr__(self): + ex.print_stacktrace() + print(FMT_CPU + '[CPU] Halting due to exception!' + FMT_NONE) + self.halted = True + + if launch_debugger: + launch_debug_session(self) + + def run(self, verbose=False): + while not self.halted: + self.step(verbose) + + 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) diff --git a/riscemu/MMU.py b/riscemu/MMU.py index f5d0375..ff59c5c 100644 --- a/riscemu/MMU.py +++ b/riscemu/MMU.py @@ -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) diff --git a/riscemu/__main__.py b/riscemu/__main__.py index 9ec6d4d..87db6bb 100644 --- a/riscemu/__main__.py +++ b/riscemu/__main__.py @@ -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 + cpu = UserModeCPU(ins_to_load) + + opts = AssemblyFileLoader.get_options(sys.argv) for file in args.files: - tk = cpu.get_tokenizer(RiscVInput.from_file(file)) - tk.tokenize() - loaded_exe = cpu.load(ExecutableParser(tk).parse()) + loader = AssemblyFileLoader.instantiate(file, opts) + + cpu.load_program(loader.parse()) # 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: - print("Error while parsing: {}".format(e.message())) - import traceback + print("Error: {}".format(e.message())) + e.print_stacktrace() - traceback.print_exception(type(e), e, e.__traceback__) sys.exit(1) diff --git a/riscemu/config.py b/riscemu/config.py index 7182958..e5f49a6 100644 --- a/riscemu/config.py +++ b/riscemu/config.py @@ -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) diff --git a/riscemu/debug.py b/riscemu/debug.py index c89d686..a186097 100644 --- a/riscemu/debug.py +++ b/riscemu/debug.py @@ -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 + diff --git a/riscemu/exceptions.py b/riscemu/exceptions.py index e9291ee..3e95dc7 100644 --- a/riscemu/exceptions.py +++ b/riscemu/exceptions.py @@ -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 diff --git a/riscemu/instructions/RV32A.py b/riscemu/instructions/RV32A.py index ba6a8a6..44c3f32 100644 --- a/riscemu/instructions/RV32A.py +++ b/riscemu/instructions/RV32A.py @@ -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 diff --git a/riscemu/instructions/RV32I.py b/riscemu/instructions/RV32I.py index e3db8a3..291ccbe 100644 --- a/riscemu/instructions/RV32I.py +++ b/riscemu/instructions/RV32I.py @@ -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) diff --git a/riscemu/instructions/RV32M.py b/riscemu/instructions/RV32M.py index 31b9341..d8ae08b 100644 --- a/riscemu/instructions/RV32M.py +++ b/riscemu/instructions/RV32M.py @@ -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 diff --git a/riscemu/instructions/__init__.py b/riscemu/instructions/__init__.py index 65bda29..96fb524 100644 --- a/riscemu/instructions/__init__.py +++ b/riscemu/instructions/__init__.py @@ -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 diff --git a/riscemu/instructions/InstructionSet.py b/riscemu/instructions/instruction_set.py similarity index 100% rename from riscemu/instructions/InstructionSet.py rename to riscemu/instructions/instruction_set.py diff --git a/riscemu/parser.py b/riscemu/parser.py index f1e94f6..711abba 100644 --- a/riscemu/parser.py +++ b/riscemu/parser.py @@ -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())) diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 9bb5fec..6fa83eb 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -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() diff --git a/riscemu/registers.py b/riscemu/registers.py index a3de09a..aa45915 100644 --- a/riscemu/registers.py +++ b/riscemu/registers.py @@ -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' diff --git a/riscemu/syscall.py b/riscemu/syscall.py index 6f22177..4bfcbbe 100644 --- a/riscemu/syscall.py +++ b/riscemu/syscall.py @@ -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): diff --git a/riscemu/tokenizer.py b/riscemu/tokenizer.py index 35dcfe9..e855b9d 100644 --- a/riscemu/tokenizer.py +++ b/riscemu/tokenizer.py @@ -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 diff --git a/riscemu/types.py b/riscemu/types.py index c175e09..b265cb9 100644 --- a/riscemu/types.py +++ b/riscemu/types.py @@ -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)