diff --git a/riscemu/CPU.py b/riscemu/CPU.py index 4223842..b9b585e 100644 --- a/riscemu/CPU.py +++ b/riscemu/CPU.py @@ -1,15 +1,23 @@ +""" +RiscEmu (c) 2021 Anton Lydike + +SPDX-License-Identifier: BSD-2-Clause + +This file contains the CPU logic (not the individual instruction sets). See instructions/InstructionSet.py for more info +on them. +""" import sys -import traceback from typing import Tuple, List, Dict, Callable, Type from .Tokenizer import RiscVTokenizer -from .Syscall import * -from .Exceptions import * +from .Syscall import SyscallInterface +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 @@ -19,7 +27,18 @@ if typing.TYPE_CHECKING: class 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. + """ def __init__(self, conf: RunConfig, instruction_sets: List[Type['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 @@ -48,6 +67,8 @@ class CPU: def get_tokenizer(self, tokenizer_input): """ Returns a tokenizer that respects the language of the CPU + + :param tokenizer_input: an instance of the RiscVTokenizerInput class """ return RiscVTokenizer(tokenizer_input, self.all_instructions()) @@ -70,11 +91,16 @@ class CPU: def continue_from_debugger(self, verbose=True): """ - dalled from the debugger to continue running + called from the debugger to continue running + + :param verbose: If True, will print each executed instruction to STDOUT """ self.__run(verbose) def step(self): + """ + Execute a single instruction, then return. + """ if self.exit: print(FMT_CPU + "[CPU] Program exited with code {}".format(self.exit_code) + FMT_NONE) else: @@ -131,11 +157,18 @@ class CPU: raise RuntimeError("Unknown instruction: {}".format(ins)) def all_instructions(self) -> List[str]: + """ + Return a list of all instructions this CPU can execute. + """ return list(self.instructions.keys()) def __repr__(self): - return "CPU(pc=0x{:08X}, cycle={}, instructions={})".format( + """ + Returns a representation of the CPU and some of its state. + """ + return "CPU(pc=0x{:08X}, cycle={}, exit={}, instructions={})".format( self.pc, self.cycle, + self.exit, " ".join(s.name for s in self.instruction_sets) ) diff --git a/riscemu/__init__.py b/riscemu/__init__.py index bb4e9d5..e80c898 100644 --- a/riscemu/__init__.py +++ b/riscemu/__init__.py @@ -1,15 +1,15 @@ -from .Exceptions import * +from .Exceptions import RiscemuBaseException, LaunchDebuggerException, InvalidSyscallException, LinkerException, \ + ParseException, NumberFormatException, InvalidRegisterException, MemoryAccessException, OutOfMemoryException -from .Tokenizer import RiscVToken, RiscVInput, RiscVTokenizer, RiscVInstructionToken, RiscVSymbolToken, \ - RiscVPseudoOpToken, TokenType +from .Tokenizer import RiscVInput, RiscVTokenizer - -from .Executable import Executable, LoadedExecutable, LoadedMemorySection, LoadedInstruction +from .Executable import Executable, LoadedExecutable from .ExecutableParser import ExecutableParser from .MMU import MMU +from .Registers import Registers +from .Syscall import SyscallInterface, Syscall +from .CPU import CPU -from .CPU import CPU, Registers, Syscall, SyscallInterface - -from .Config import RunConfig \ No newline at end of file +from .Config import RunConfig diff --git a/riscemu/__main__.py b/riscemu/__main__.py index 9a07df0..444710c 100644 --- a/riscemu/__main__.py +++ b/riscemu/__main__.py @@ -1,3 +1,11 @@ +""" +RiscEmu (c) 2021 Anton Lydike + +SPDX-License-Identifier: MIT + +This file holds the logic for starting the emulator from the CLI +""" + if __name__ == '__main__': from . import * from .helpers import * diff --git a/riscemu/instructions/InstructionSet.py b/riscemu/instructions/InstructionSet.py index 7f7b712..c7dfd49 100644 --- a/riscemu/instructions/InstructionSet.py +++ b/riscemu/instructions/InstructionSet.py @@ -1,12 +1,25 @@ -import typing +""" +RiscEmu (c) 2021 Anton Lydike -from abc import ABC, abstractmethod -from ..CPU import * +SPDX-License-Identifier: BSD-2-Clause +""" + +from typing import Tuple, Callable, Dict + +from abc import ABC +from ..CPU import CPU +from ..helpers import ASSERT_LEN, ASSERT_IN, to_unsigned class InstructionSet(ABC): """ Represents a collection of instructions + + Each instruction set has to inherit from this class. Each instruction should be it's own method: + + instruction_(self, ins: LoadedInstruction) + + instructions containing a dot '.' should replace it with an underscore. """ def __init__(self, cpu: 'CPU'): @@ -15,7 +28,7 @@ class InstructionSet(ABC): self.mmu = cpu.mmu self.regs = cpu.regs - def load(self) -> typing.Dict[str, typing.Callable[['LoadedInstruction'], None]]: + def load(self) -> Dict[str, Callable[['LoadedInstruction'], None]]: """ This is called by the CPU once it instantiates this instruction set @@ -27,13 +40,18 @@ class InstructionSet(ABC): } def get_instructions(self): + """ + Returns a list of all valid instruction names included in this instruction set + + converts underscores in names to dots + """ for member in dir(self): if member.startswith('instruction_'): yield member[12:].replace('_', '.'), getattr(self, member) def parse_mem_ins(self, ins: 'LoadedInstruction') -> Tuple[str, int]: """ - parses both rd, rs, imm and rd, imm(rs) arguments and returns (rd, imm+rs1) + parses both rd, rs, imm and rd, imm(rs) argument format and returns (rd, imm+rs1) (so a register and address tuple for memory instructions) """ if len(ins.args) == 3: @@ -101,6 +119,9 @@ class InstructionSet(ABC): @property def pc(self): + """ + shorthand for self.cpu.pc + """ return self.cpu.pc @pc.setter diff --git a/riscemu/instructions/RV32I.py b/riscemu/instructions/RV32I.py index 7785716..4c0aa55 100644 --- a/riscemu/instructions/RV32I.py +++ b/riscemu/instructions/RV32I.py @@ -1,10 +1,27 @@ +""" +RiscEmu (c) 2021 Anton Lydike + +SPDX-License-Identifier: BSD-2-Clause +""" + from .InstructionSet import * from ..helpers import int_from_bytes, int_to_bytes, to_unsigned, to_signed from ..colors import FMT_DEBUG, FMT_NONE +from ..debug import launch_debug_session +from ..Exceptions import LaunchDebuggerException +from ..Syscall import Syscall class RV32I(InstructionSet): + """ + The RV32I instruction set. Some instructions are missing, such as + fence, fence.i, rdcycle, rdcycleh, rdtime, rdtimeh, rdinstret, rdinstreth + All atomic read/writes are also not implemented yet + + See https://maxvytech.com/images/RV32I-11-2018.pdf for a more detailed overview + """ + def instruction_lb(self, ins: 'LoadedInstruction'): rd, addr = self.parse_mem_ins(ins) self.regs.set(rd, int_from_bytes(self.mmu.read(addr, 1))) @@ -281,13 +298,13 @@ class RV32I(InstructionSet): def instruction_sbreak(self, ins: 'LoadedInstruction'): 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() launch_debug_session( self.cpu, self.mmu, self.regs, - "Debug instruction encountered at 0x{:08X}".format(self.pc-1) + "Debug instruction encountered at 0x{:08X}".format(self.pc - 1) ) def instruction_nop(self, ins: 'LoadedInstruction'): diff --git a/riscemu/instructions/RV32M.py b/riscemu/instructions/RV32M.py index 15816d2..5ffbcf4 100644 --- a/riscemu/instructions/RV32M.py +++ b/riscemu/instructions/RV32M.py @@ -1,8 +1,17 @@ +""" +RiscEmu (c) 2021 Anton Lydike + +SPDX-License-Identifier: BSD-2-Clause +""" + from .InstructionSet import * -from ..helpers import int_from_bytes, int_to_bytes, to_unsigned, to_signed +from ..Exceptions import INS_NOT_IMPLEMENTED class RV32M(InstructionSet): + """ + The RV32M Instruction set, containing multiplication and division instructions + """ def instruction_mul(self, ins: 'LoadedInstruction'): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) self.regs.set( diff --git a/riscemu/instructions/__init__.py b/riscemu/instructions/__init__.py index 095ef56..be93449 100644 --- a/riscemu/instructions/__init__.py +++ b/riscemu/instructions/__init__.py @@ -1,3 +1,11 @@ +""" +RiscEmu (c) 2021 Anton Lydike + +SPDX-License-Identifier: BSD-2-Clause + +This package holds all instruction sets, available to the processor +""" + from .InstructionSet import InstructionSet from .RV32M import RV32M from .RV32I import RV32I