added lots of documentation in pydoc style
This commit is contained in:
parent
8c1714116e
commit
2a68f16e99
@ -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)
|
||||
)
|
||||
|
@ -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
|
||||
from .Config import RunConfig
|
||||
|
@ -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 *
|
||||
|
@ -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_<name>(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
|
||||
|
@ -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'):
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user