diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a5f27f..d9894e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,14 @@ # Upcoming + - Feature: Added Zicsr extension and with that support for CSRs - Feature: Starting to add support for Snitch architecture (Xssr) - Feature: Add support for `.p2align` assembler directive - Rework: Improve handling of immediates, so that `beq a0, a1, 1b` and `beq a0, a1, -16` can both can be handled correctly. - BugFix: Fix some more errors in the RV32F implementation - Dev: Move to poetry for project development environment + - Dev: Module refactoring, core datastructures now mostly live inside riscemu.core + - Perf: Improved performance by around 1.8x ## 2.1.1 diff --git a/examples/estimate-cpu-freq.asm b/examples/estimate-cpu-freq.asm new file mode 100644 index 0000000..b088caa --- /dev/null +++ b/examples/estimate-cpu-freq.asm @@ -0,0 +1,180 @@ +.data +// 16 words of data for benchmarking loads/stores +buf0: + .word 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 + +.text +.globl main + +main: + mv s5, ra + + // warmup + printf "warmup" + li a0, 1000 + jal nop_loop + + la a1, nop_loop + li a0, 1000 + printf "Measuring nops:" + jal measure_loop + li a0, 10000 + jal measure_loop + + la a1, arith_loop + li a0, 1000 + printf "Measuring addi:" + jal measure_loop + li a0, 10000 + jal measure_loop + + la a1, memory_loop + la a2, buf0 + li a0, 1000 + printf "Measuring load/stores:" + jal measure_loop + li a0, 10000 + jal measure_loop + + mv ra, s5 + ret + + +// rtclock tickrate is 32768 +// execute bench at addr a1, a0 times +measure_loop: + mv s4, ra + csrrs s0, zero, cycle + csrrs s2, zero, time + jalr ra, a1, 0 + csrrs s1, zero, cycle + csrrs s3, zero, time + sub s0, s1, s0 + sub s1, s3, s2 + fcvt.s.w ft0, s1 + li t1, 32768 // cpu tickrate + fcvt.s.w ft1, t1 + fdiv.s ft0, ft0, ft1 // ft0 = seconds of execution time + fcvt.s.w ft1, s0 // ft1 = number of ins executed + fdiv.s ft2, ft1, ft0 // ft2 = ins/second + li t0, 1000 + fcvt.s.w ft1, t0 // ft1 = 1k + fdiv.s ft2, ft2, ft1 // ft2 = kins/sec + + printf "executed {} instructions in {:.4f} seconds ({:.2f}ki/s)", s0, ft0, ft2 + mv ra, s4 + ret + +// first loop, executes a0*32 nop + a0 addi + a0 beq instructions (a0 > 1) +nop_loop: + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + addi a0, a0, -1 + blt zero, a0, nop_loop + ret + +// second loop, executes a0*16 load/store pairs + a0 addi + a0 beq instructions (a0 > 1) +memory_loop: + lw t0, 0(a2) + sw t0, 0(a2) + lw t0, 4(a2) + sw t0, 4(a2) + lw t0, 8(a2) + sw t0, 8(a2) + lw t0, 12(a2) + sw t0, 12(a2) + lw t0, 16(a2) + sw t0, 16(a2) + lw t0, 20(a2) + sw t0, 20(a2) + lw t0, 24(a2) + sw t0, 24(a2) + lw t0, 28(a2) + sw t0, 28(a2) + lw t0, 32(a2) + sw t0, 32(a2) + lw t0, 36(a2) + sw t0, 36(a2) + lw t0, 40(a2) + sw t0, 40(a2) + lw t0, 44(a2) + sw t0, 44(a2) + lw t0, 48(a2) + sw t0, 48(a2) + lw t0, 52(a2) + sw t0, 52(a2) + lw t0, 56(a2) + sw t0, 56(a2) + lw t0, 60(a2) + sw t0, 60(a2) + addi a0, a0, -1 + bge a0, zero, nop_loop + ret + +// third loop, executes a0*32 addi + a0 addi + a0 beq instructions (a0 > 1) +arith_loop: + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi t0, a0, 1234 + addi a0, a0, -1 + blt zero, a0, nop_loop + ret diff --git a/riscemu/IO/IOModule.py b/riscemu/IO/IOModule.py index 3af7f7a..e2ddbc6 100644 --- a/riscemu/IO/IOModule.py +++ b/riscemu/IO/IOModule.py @@ -1,7 +1,7 @@ from abc import ABC from typing import Optional -from riscemu.types import MemorySection, MemoryFlags, T_RelativeAddress +from riscemu.core import MemorySection, MemoryFlags, T_RelativeAddress class IOModule(MemorySection, ABC): diff --git a/riscemu/IO/TextIO.py b/riscemu/IO/TextIO.py index 30d790d..8f0ebfc 100644 --- a/riscemu/IO/TextIO.py +++ b/riscemu/IO/TextIO.py @@ -1,6 +1,6 @@ from .IOModule import IOModule -from ..priv.Exceptions import InstructionAccessFault -from ..types import T_RelativeAddress, Instruction, MemoryFlags, Int32 +from core.traps import InstructionAccessFault +from ..core import T_RelativeAddress, Instruction, MemoryFlags, Int32 class TextIO(IOModule): diff --git a/riscemu/__init__.py b/riscemu/__init__.py index 27f8dbc..80c7ccf 100644 --- a/riscemu/__init__.py +++ b/riscemu/__init__.py @@ -8,33 +8,9 @@ It contains everything needed to run assembly files, so you don't need any custom compilers or toolchains """ -from .types.exceptions import ( - RiscemuBaseException, - LaunchDebuggerException, - InvalidSyscallException, - LinkerException, - ParseException, - NumberFormatException, - InvalidRegisterException, - MemoryAccessException, - OutOfMemoryException, -) - -from .instructions import * - -from .MMU import MMU -from .registers import Registers -from .syscall import SyscallInterface, Syscall -from .CPU import CPU, UserModeCPU -from .debug import launch_debug_session - -from .config import RunConfig - -from .parser import tokenize, parse_tokens, AssemblyFileLoader - # to read package version: import importlib.metadata __author__ = "Anton Lydike " __copyright__ = "Copyright 2023 Anton Lydike" -__version__ = importlib.metadata.version(__name__) +__version__ = importlib.metadata.version("riscemu") diff --git a/riscemu/__main__.py b/riscemu/__main__.py index d1dc02f..a445769 100644 --- a/riscemu/__main__.py +++ b/riscemu/__main__.py @@ -7,8 +7,8 @@ """ import sys -from riscemu import RiscemuBaseException -from riscemu.riscemu_main import RiscemuMain +from .core import RiscemuBaseException +from .riscemu_main import RiscemuMain def main(): diff --git a/riscemu/assembler.py b/riscemu/assembler.py index ec54d94..48bcd8b 100644 --- a/riscemu/assembler.py +++ b/riscemu/assembler.py @@ -3,10 +3,10 @@ from typing import Optional, Tuple, Union from .colors import FMT_PARSE, FMT_NONE -from riscemu.types.exceptions import ParseException, ASSERT_LEN +from riscemu.core.exceptions import ParseException, ASSERT_LEN from .helpers import parse_numeric_argument, align_addr, get_section_base_name from .tokenizer import Token -from .types import ( +from .core import ( Program, T_RelativeAddress, InstructionContext, diff --git a/riscemu/config.py b/riscemu/config.py index ee80a96..35edf27 100644 --- a/riscemu/config.py +++ b/riscemu/config.py @@ -24,3 +24,6 @@ class RunConfig: # runtime config use_libc: bool = False ignore_exit_code: bool = False + # csr stuff: + # frequency of the real-time clock + rtclock_tickrate: int = 32768 diff --git a/riscemu/types/__init__.py b/riscemu/core/__init__.py similarity index 82% rename from riscemu/types/__init__.py rename to riscemu/core/__init__.py index fdbe887..4595974 100644 --- a/riscemu/types/__init__.py +++ b/riscemu/core/__init__.py @@ -10,20 +10,6 @@ NUMBER_SYMBOL_PATTERN = re.compile(r"^\d+[fb]$") -# base classes -from .flags import MemoryFlags -from .int32 import UInt32, Int32 -from .float32 import Float32 -from .instruction import Instruction, Immediate -from .instruction_context import InstructionContext -from .memory_section import MemorySection -from .program import Program -from .program_loader import ProgramLoader -from .cpu import CPU -from .simple_instruction import SimpleInstruction -from .instruction_memory_section import InstructionMemorySection -from .binary_data_memory_section import BinaryDataMemorySection - # exceptions from .exceptions import ( ParseException, @@ -39,3 +25,23 @@ UnimplementedInstruction, INS_NOT_IMPLEMENTED, ) + +# base classes +from .flags import MemoryFlags +from .int32 import UInt32, Int32 +from .float32 import Float32 +from .rtclock import RTClock +from .instruction import Instruction, Immediate, InstructionWithEncoding +from .instruction_context import InstructionContext +from .memory_section import MemorySection +from .program import Program +from .program_loader import ProgramLoader +from .privmodes import PrivModes +from .mmu import MMU +from .csr import CSR +from .registers import Registers +from .cpu import CPU +from .simple_instruction import SimpleInstruction +from .instruction_memory_section import InstructionMemorySection +from .binary_data_memory_section import BinaryDataMemorySection +from .usermode_cpu import UserModeCPU diff --git a/riscemu/types/binary_data_memory_section.py b/riscemu/core/binary_data_memory_section.py similarity index 96% rename from riscemu/types/binary_data_memory_section.py rename to riscemu/core/binary_data_memory_section.py index 11570a9..7ce3b73 100644 --- a/riscemu/types/binary_data_memory_section.py +++ b/riscemu/core/binary_data_memory_section.py @@ -6,7 +6,7 @@ T_RelativeAddress, Instruction, ) -from ..types.exceptions import MemoryAccessException +from ..core.exceptions import MemoryAccessException class BinaryDataMemorySection(MemorySection): diff --git a/riscemu/types/cpu.py b/riscemu/core/cpu.py similarity index 61% rename from riscemu/types/cpu.py rename to riscemu/core/cpu.py index 4dd7bc4..a2dcd0c 100644 --- a/riscemu/types/cpu.py +++ b/riscemu/core/cpu.py @@ -1,13 +1,23 @@ from abc import ABC, abstractmethod -from typing import List, Type, Callable, Set, Dict, TYPE_CHECKING, Iterable +from typing import List, Type, Callable, Set, Dict, TYPE_CHECKING -from ..registers import Registers from ..config import RunConfig from ..colors import FMT_NONE, FMT_CPU -from . import T_AbsoluteAddress, Instruction, Program, ProgramLoader +from . import ( + T_AbsoluteAddress, + Instruction, + Program, + PrivModes, + MMU, + UInt32, + Registers, + CSR, + RTClock, + csr_constants, +) if TYPE_CHECKING: - from ..MMU import MMU + # from core.mmu import MMU from ..instructions import InstructionSet @@ -32,6 +42,12 @@ class CPU(ABC): # configuration conf: RunConfig + # control and status things: + hart_id: int + mode: PrivModes + csr: CSR + rtclock: RTClock + def __init__( self, mmu: "MMU", @@ -53,7 +69,10 @@ def __init__( self.halted = False self.cycle = 0 self.pc = 0 + self.hart_id = 0 self.debugger_active = False + self.csr = CSR() + self.rtclock = RTClock(conf.rtclock_tickrate) def run_instruction(self, ins: Instruction): """ @@ -61,11 +80,10 @@ def run_instruction(self, ins: Instruction): :param ins: The instruction to execute """ - if ins.name in self.instructions: + try: self.instructions[ins.name](ins) - else: - # this should never be reached, as unknown instructions are imparseable - raise RuntimeError("Unknown instruction: {}".format(ins)) + except KeyError as ex: + raise RuntimeError("Unknown instruction: {}".format(ins)) from ex def load_program(self, program: Program): self.mmu.load_program(program) @@ -90,12 +108,19 @@ def step(self, verbose: bool = False): def run(self, verbose: bool = False): pass + def initialize_registers(self): + # set a0 to the hartid + self.regs.set("a0", UInt32(self.hart_id)) + def launch(self, verbose: bool = False): entrypoint = self.mmu.find_entrypoint() if entrypoint is None: entrypoint = self.mmu.programs[0].entrypoint + self.initialize_registers() + self.setup_csr() + if self.conf.verbosity > 0: print( FMT_CPU @@ -107,14 +132,6 @@ def launch(self, verbose: bool = False): self.pc = entrypoint self.run(verbose) - @classmethod - @abstractmethod - def get_loaders(cls) -> Iterable[Type[ProgramLoader]]: - pass - - def get_best_loader_for(self, file_name: str) -> Type[ProgramLoader]: - return max(self.get_loaders(), key=lambda ld: ld.can_parse(file_name)) - @property def sections(self): return self.mmu.sections @@ -122,3 +139,35 @@ def sections(self): @property def programs(self): return self.mmu.programs + + def setup_csr(self): + """ + Set up standard CSR registers, can be hooked into when subclassing to provide + more information. + """ + + self.csr.set(CSR.name_to_addr("mhartid"), UInt32(self.hart_id)) + self.csr.register_callback( + CSR.name_to_addr("time"), + getter=self.rtclock.get_low32, + ) + self.csr.register_callback( + CSR.name_to_addr("timeh"), + getter=self.rtclock.get_hi32, + ) + self.csr.register_callback( + CSR.name_to_addr("instret"), + getter=(lambda csr, _: UInt32(self.cycle)), + ) + self.csr.register_callback( + CSR.name_to_addr("instreth"), + getter=(lambda csr, _: UInt32(self.cycle >> 32)), + ) + self.csr.register_callback( + CSR.name_to_addr("cycle"), + getter=(lambda csr, _: UInt32(self.cycle)), + ) + self.csr.register_callback( + CSR.name_to_addr("cycleh"), + getter=(lambda csr, _: UInt32(self.cycle >> 32)), + ) diff --git a/riscemu/core/csr.py b/riscemu/core/csr.py new file mode 100644 index 0000000..24f9328 --- /dev/null +++ b/riscemu/core/csr.py @@ -0,0 +1,259 @@ +from collections import defaultdict + +from typing import Dict, Callable, Optional, Union +from . import UInt32, PrivModes, csr_constants +from .traps import InstructionAccessFault + +from ..colors import FMT_ERROR, FMT_NONE + + +def _invalid_setter(addr: int, old_val: UInt32, new_val: UInt32): + print(FMT_ERROR + f"Cannot write csr 0x{addr:03X}" + FMT_NONE) + raise InstructionAccessFault(addr) + + +def _invalid_getter(addr: int, curr_val: UInt32) -> UInt32: + print(FMT_ERROR + f"Cannot read csr 0x{addr:03X}" + FMT_NONE) + raise InstructionAccessFault(addr) + + +class MStatusRegister: + """ + helper for the mstatus register + """ + + state: UInt32 + + def __init__(self): + self.state = UInt32() + + @property + def uie(self) -> UInt32: + return (self.state & (1 << 0)) >> 0 + + @uie.setter + def uie(self, new_val: UInt32): + new_val = new_val ^ self.uie + self.state = self.state ^ (new_val << 0) + + @property + def sie(self) -> UInt32: + return (self.state & (1 << 1)) >> 1 + + @sie.setter + def sie(self, new_val: UInt32): + new_val = new_val ^ self.sie + self.state = self.state ^ (new_val << 1) + + @property + def mie(self) -> UInt32: + return (self.state & (1 << 3)) >> 3 + + @mie.setter + def mie(self, new_val: UInt32): + new_val = new_val ^ self.mie + self.state = self.state ^ (new_val << 3) + + @property + def upie(self) -> UInt32: + return (self.state & (1 << 4)) >> 4 + + @upie.setter + def upie(self, new_val: UInt32): + new_val = new_val ^ self.upie + self.state = self.state ^ (new_val << 4) + + @property + def spie(self) -> UInt32: + return (self.state & (1 << 5)) >> 5 + + @spie.setter + def spie(self, new_val: UInt32): + new_val = new_val ^ self.spie + self.state = self.state ^ (new_val << 5) + + @property + def mpie(self) -> UInt32: + return (self.state & (1 << 7)) >> 7 + + @mpie.setter + def mpie(self, new_val: UInt32): + new_val = new_val ^ self.mpie + self.state = self.state ^ (new_val << 7) + + @property + def spp(self) -> UInt32: + return (self.state & (1 << 8)) >> 8 + + @spp.setter + def spp(self, new_val: UInt32): + new_val = new_val ^ self.spp + self.state = self.state ^ (new_val << 8) + + @property + def mpp(self) -> UInt32: + # bitwidth = 2 + return (self.state & (0b11 << 11)) >> 11 + + @mpp.setter + def mpp(self, new_val: UInt32): + new_val = new_val ^ self.mpp + self.state = self.state ^ (new_val << 11) + + @property + def fs(self) -> UInt32: + # bitwidth = 2 + return (self.state & (0b11 << 13)) >> 13 + + @fs.setter + def fs(self, new_val: UInt32): + new_val = new_val ^ self.fs + self.state = self.state ^ (new_val << 13) + + @property + def xs(self) -> UInt32: + # bitwidth = 2 + return (self.state & (0b11 << 15)) >> 15 + + @xs.setter + def xs(self, new_val: UInt32): + new_val = new_val ^ self.xs + self.state = self.state ^ (new_val << 15) + + @property + def mpriv(self) -> UInt32: + return (self.state & (1 << 17)) >> 17 + + @mpriv.setter + def mpriv(self, new_val: UInt32): + new_val = new_val ^ self.mpriv + self.state = self.state ^ (new_val << 17) + + @property + def sum(self) -> UInt32: + return (self.state & (1 << 18)) >> 18 + + @sum.setter + def sum(self, new_val: UInt32): + new_val = new_val ^ self.sum + self.state = self.state ^ (new_val << 18) + + @property + def mxr(self) -> UInt32: + return (self.state & (1 << 19)) >> 19 + + @mxr.setter + def mxr(self, new_val: UInt32): + new_val = new_val ^ self.mxr + self.state = self.state ^ (new_val << 19) + + @property + def tvm(self) -> UInt32: + return (self.state & (1 << 20)) >> 20 + + @tvm.setter + def tvm(self, new_val: UInt32): + new_val = new_val ^ self.tvm + self.state = self.state ^ (new_val << 20) + + @property + def tw(self) -> UInt32: + return (self.state & (1 << 21)) >> 21 + + @tw.setter + def tw(self, new_val: UInt32): + new_val = new_val ^ self.tw + self.state = self.state ^ (new_val << 21) + + @property + def tsr(self) -> UInt32: + return (self.state & (1 << 22)) >> 22 + + @tsr.setter + def tsr(self, new_val: UInt32): + new_val = new_val ^ self.tsr + self.state = self.state ^ (new_val << 22) + + @property + def sd(self) -> UInt32: + return (self.state & (1 << 31)) >> 31 + + @sd.setter + def sd(self, new_val: UInt32): + new_val = new_val ^ self.sd + self.state = self.state ^ (new_val << 31) + + +class CSR: + """ + Represents a processors control and status registers + """ + + state: Dict[int, UInt32] + + setters: Dict[int, Callable[[int, UInt32, UInt32], UInt32]] + getters: Dict[int, Callable[[int, UInt32], UInt32]] + + mstatus: MStatusRegister + + def __init__(self): + self.state = defaultdict(UInt32) + self.mstatus = MStatusRegister() + # wire mstatus state up to our csr state + self.state[CSR.name_to_addr("mstatus")] = self.mstatus.state + + self.getters = dict() + self.setters = dict() + + def get(self, addr: int) -> UInt32: + if addr in self.getters: + return self.getters[addr](addr, self.state[addr]) + return self.state[addr] + + def set(self, addr: int, val: UInt32): + if addr in self.setters: + self.state[addr] = self.setters[addr](addr, self.state[addr], val) + else: + self.state[addr] = val + + def register_callback( + self, + addr: int, + *, + getter: Optional[Callable[[int, UInt32], UInt32]] = None, + setter: Optional[Callable[[int, UInt32, UInt32], UInt32]] = None, + ): + """ + addr: the CSR address + getter: a function mapping (addr, old_val_in_store) -> actual value + setter: a function mapping (addr, old_val_in_store, given_val) -> new_val_in_store + + where the _in_store values denote the values in the CSR store. + Values may be handled by external stores, if that's the case + these arguments can be safely ignored. + """ + if getter is None: + getter = _invalid_getter + self.getters[addr] = getter + if setter is None: + setter = _invalid_setter + self.setters[addr] = setter + + @staticmethod + def assert_can_read(mode: PrivModes, addr: int): + if (addr >> 8) & 3 > mode.value: + raise InstructionAccessFault(addr) + + @staticmethod + def assert_can_write(mode: PrivModes, addr: int): + if (addr >> 8) & 3 > mode.value or addr >> 10 == 0b11: + raise InstructionAccessFault(addr) + + @staticmethod + def name_to_addr(addr: Union[str, int]) -> Optional[int]: + if isinstance(addr, str): + if addr not in csr_constants.CSR_NAME_TO_ADDR: + print("Unknown CSR register {}".format(addr)) + return None + return csr_constants.CSR_NAME_TO_ADDR[addr] + return addr diff --git a/riscemu/priv/CSRConsts.py b/riscemu/core/csr_constants.py similarity index 77% rename from riscemu/priv/CSRConsts.py rename to riscemu/core/csr_constants.py index 96e6ecf..84992b0 100644 --- a/riscemu/priv/CSRConsts.py +++ b/riscemu/core/csr_constants.py @@ -29,35 +29,10 @@ Assigns tuple (interrupt bit, exception code) to their respective readable names """ -MSTATUS_OFFSETS: Dict[str, int] = { - "uie": 0, - "sie": 1, - "mie": 3, - "upie": 4, - "spie": 5, - "mpie": 7, - "spp": 8, - "mpp": 11, - "fs": 13, - "xs": 15, - "mpriv": 17, - "sum": 18, - "mxr": 19, - "tvm": 20, - "tw": 21, - "tsr": 22, - "sd": 31, -} -""" -Offsets for all mstatus bits -""" - -MSTATUS_LEN_2 = ("mpp", "fs", "xs") -""" -All mstatus parts that have length 2. All other mstatus parts have length 1 -""" - CSR_NAME_TO_ADDR: Dict[str, int] = { + "fflags": 0x001, + "frm": 0x002, + "fcsr": 0x003, "mstatus": 0x300, "misa": 0x301, "mie": 0x304, @@ -66,15 +41,19 @@ "mcause": 0x342, "mtval": 0x343, "mip": 0x344, + "mtimecmp": 0x780, + "mtimecmph": 0x781, + "halt": 0x789, + "cycle": 0xC00, + "time": 0xC01, + "instret": 0xC02, + "cycleh": 0xC80, + "timeh": 0xC81, + "instreth": 0xC82, "mvendorid": 0xF11, "marchid": 0xF12, "mimpid": 0xF13, "mhartid": 0xF14, - "time": 0xC01, - "timeh": 0xC81, - "halt": 0x789, - "mtimecmp": 0x780, - "mtimecmph": 0x781, } """ Translation for named registers diff --git a/riscemu/types/exceptions.py b/riscemu/core/exceptions.py similarity index 100% rename from riscemu/types/exceptions.py rename to riscemu/core/exceptions.py diff --git a/riscemu/types/flags.py b/riscemu/core/flags.py similarity index 100% rename from riscemu/types/flags.py rename to riscemu/core/flags.py diff --git a/riscemu/types/float32.py b/riscemu/core/float32.py similarity index 97% rename from riscemu/types/float32.py rename to riscemu/core/float32.py index adf50c0..9440b84 100644 --- a/riscemu/types/float32.py +++ b/riscemu/core/float32.py @@ -71,7 +71,9 @@ def __mul__(self, other: Union["Float32", float]): return self.__class__(self.value * other) def __truediv__(self, other: Any): - return self // other + if isinstance(other, Float32): + other = other.value + return self.__class__(self.value / other) def __floordiv__(self, other: Any): if isinstance(other, Float32): diff --git a/riscemu/types/instruction.py b/riscemu/core/instruction.py similarity index 88% rename from riscemu/types/instruction.py rename to riscemu/core/instruction.py index 45a98b5..9dd2f07 100644 --- a/riscemu/types/instruction.py +++ b/riscemu/core/instruction.py @@ -53,3 +53,14 @@ def get_reg(self, num: int) -> str: def __repr__(self): return "{} {}".format(self.name, ", ".join(self.args)) + + +class InstructionWithEncoding(ABC): + """ + Mixin for instructions that have encodings + """ + + @property + @abstractmethod + def encoding(self) -> int: + raise NotImplementedError() diff --git a/riscemu/types/instruction_context.py b/riscemu/core/instruction_context.py similarity index 95% rename from riscemu/types/instruction_context.py rename to riscemu/core/instruction_context.py index eff5cf1..4cbc59f 100644 --- a/riscemu/types/instruction_context.py +++ b/riscemu/core/instruction_context.py @@ -2,7 +2,7 @@ from typing import Dict, List, Optional from .exceptions import ParseException -from ..types import T_AbsoluteAddress, T_RelativeAddress, NUMBER_SYMBOL_PATTERN +from ..core import T_AbsoluteAddress, T_RelativeAddress, NUMBER_SYMBOL_PATTERN class InstructionContext: diff --git a/riscemu/types/instruction_memory_section.py b/riscemu/core/instruction_memory_section.py similarity index 100% rename from riscemu/types/instruction_memory_section.py rename to riscemu/core/instruction_memory_section.py diff --git a/riscemu/types/int32.py b/riscemu/core/int32.py similarity index 97% rename from riscemu/types/int32.py rename to riscemu/core/int32.py index cf524c4..e619f5d 100644 --- a/riscemu/types/int32.py +++ b/riscemu/core/int32.py @@ -9,7 +9,7 @@ class Int32: It implements basically all mathematical dunder magic methods (__add__, __sub__, etc.) You can use it just like you would any other integer, just be careful when passing it - to functions which actually expect an integer and not a Int32. + to functions which actually expect an integer and not an Int32. """ _type = c_int32 @@ -18,17 +18,15 @@ class Int32: def __init__( self, val: Union[int, c_int32, c_uint32, "Int32", bytes, bytearray, bool] = 0 ): - if isinstance(val, (bytes, bytearray)): + if isinstance(val, (c_uint32, c_int32, Int32)): + self._val = self.__class__._type(val.value) + elif isinstance(val, (int, bool)): + self._val = self.__class__._type(val) + elif isinstance(val, (bytes, bytearray)): signed = len(val) == 4 and self._type == c_int32 self._val = self.__class__._type( int.from_bytes(val, "little", signed=signed) ) - elif isinstance(val, self.__class__._type): - self._val = val - elif isinstance(val, (c_uint32, c_int32, Int32)): - self._val = self.__class__._type(val.value) - elif isinstance(val, (int, bool)): - self._val = self.__class__._type(val) else: raise RuntimeError( "Unknown {} input type: {} ({})".format( @@ -100,6 +98,9 @@ def __eq__(self, other: object) -> bool: def __neg__(self): return self.__class__(-self._val.value) + def __invert__(self): + return self.__class__(~self.value) + def __abs__(self): return self.__class__(abs(self.value)) diff --git a/riscemu/types/memory_section.py b/riscemu/core/memory_section.py similarity index 100% rename from riscemu/types/memory_section.py rename to riscemu/core/memory_section.py diff --git a/riscemu/MMU.py b/riscemu/core/mmu.py similarity index 81% rename from riscemu/MMU.py rename to riscemu/core/mmu.py index 3c99644..2890f87 100644 --- a/riscemu/MMU.py +++ b/riscemu/core/mmu.py @@ -6,9 +6,9 @@ from typing import Dict, List, Optional, Union -from .colors import * -from .helpers import align_addr -from .types import ( +from ..colors import * +from ..helpers import align_addr +from . import ( Instruction, MemorySection, MemoryFlags, @@ -17,8 +17,9 @@ InstructionContext, Int32, Float32, + InvalidAllocationException, + MemoryAccessException, ) -from .types.exceptions import InvalidAllocationException, MemoryAccessException class MMU: @@ -53,6 +54,16 @@ class MMU: The global symbol table """ + _ins_sec: Optional[MemorySection] + """ + Caching the last section where we read instructions from + """ + + _mem_sec: Optional[MemorySection] + """ + Caching the last section where we read data from + """ + def __init__(self): """ Create a new MMU @@ -60,6 +71,8 @@ def __init__(self): self.programs = list() self.sections = list() self.global_symbols = dict() + self._ins_sec = None + self._mem_sec = None def get_sec_containing(self, addr: T_AbsoluteAddress) -> Optional[MemorySection]: """ @@ -70,6 +83,7 @@ def get_sec_containing(self, addr: T_AbsoluteAddress) -> Optional[MemorySection] """ for sec in self.sections: if sec.base <= addr < sec.base + sec.size: + self._mem_sec = sec return sec return None @@ -86,17 +100,21 @@ def read_ins(self, addr: T_AbsoluteAddress) -> Instruction: :param addr: The location :return: The Instruction """ - sec = self.get_sec_containing(addr) - if sec is None: - print( - FMT_MEM - + "[MMU] Trying to read instruction form invalid region! (read at {}) ".format( - addr + sec = self._ins_sec + if addr < sec.base or sec.base + sec.size <= addr: + sec = self.get_sec_containing(addr) + if sec is not None: + self._ins_sec = sec + else: + 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) def read(self, addr: Union[int, Int32], size: int) -> bytearray: @@ -107,20 +125,22 @@ def read(self, addr: Union[int, Int32], size: int) -> bytearray: :param size: The number of bytes to read :return: The bytearray at addr """ - if isinstance(addr, Int32): - addr = addr.unsigned_value - sec = self.get_sec_containing(addr) - if sec is None: - print( - FMT_MEM - + "[MMU] Trying to read data form invalid region at 0x{:x}! ".format( - addr + sec = self._mem_sec + if addr < sec.base or sec.base + sec.size <= addr: + sec = self.get_sec_containing(addr) + if sec is not None: + self._mem_sec = sec + else: + print( + FMT_MEM + + "[MMU] Trying to read data form invalid region at 0x{:x}! ".format( + addr + ) + + FMT_NONE + ) + raise MemoryAccessException( + "region is non-initialized!", addr, size, "read" ) - + FMT_NONE - ) - raise MemoryAccessException( - "region is non-initialized!", addr, size, "read" - ) return sec.read(addr - sec.base, size) def write(self, addr: int, size: int, data: bytearray): @@ -131,18 +151,22 @@ def write(self, addr: int, size: int, data: bytearray): :param size: The number of bytes to write :param data: The bytearray to write (only first size bytes are written) """ - sec = self.get_sec_containing(addr) - if sec is None: - print( - FMT_MEM - + "[MMU] Invalid write into non-initialized region at 0x{:08X}".format( - addr + sec = self._mem_sec + if addr < sec.base or sec.base + sec.size <= addr: + sec = self.get_sec_containing(addr) + if sec is not None: + self._mem_sec = sec + else: + print( + FMT_MEM + + "[MMU] Invalid write into non-initialized region at 0x{:08X}".format( + addr + ) + + FMT_NONE + ) + raise MemoryAccessException( + "region is non-initialized!", addr, size, "write" ) - + FMT_NONE - ) - raise MemoryAccessException( - "region is non-initialized!", addr, size, "write" - ) return sec.write(addr - sec.base, size, data) @@ -332,6 +356,8 @@ def _update_state(self): """ self.programs.sort(key=lambda bin: bin.base) self.sections.sort(key=lambda sec: sec.base) + self._mem_sec = self.sections[-1] + self._ins_sec = self.sections[-1] def get_guaranteed_free_address(self) -> T_AbsoluteAddress: if len(self.sections) == 0: diff --git a/riscemu/core/platform.py b/riscemu/core/platform.py new file mode 100644 index 0000000..ba670d8 --- /dev/null +++ b/riscemu/core/platform.py @@ -0,0 +1,27 @@ +from .cpu import CPU +from .memory_section import MemorySection + +from typing import List + + +class Platform: + """ + This is a wrapper around a given hardware configuration. + """ + + harts: List[CPU] + + memory: List[MemorySection] + + def __init__(self): + pass + + def step(self): + performed_step = False + + for cpu in self.harts: + if not cpu.halted: + cpu.step(cpu.conf.verbosity > 1) + performed_step = True + + return performed_step diff --git a/riscemu/priv/privmodes.py b/riscemu/core/privmodes.py similarity index 87% rename from riscemu/priv/privmodes.py rename to riscemu/core/privmodes.py index 1e58bc2..dc2fa4b 100644 --- a/riscemu/priv/privmodes.py +++ b/riscemu/core/privmodes.py @@ -4,4 +4,5 @@ class PrivModes(IntEnum): USER = 0 SUPER = 1 + HYPER = 2 MACHINE = 3 diff --git a/riscemu/types/program.py b/riscemu/core/program.py similarity index 100% rename from riscemu/types/program.py rename to riscemu/core/program.py diff --git a/riscemu/types/program_loader.py b/riscemu/core/program_loader.py similarity index 100% rename from riscemu/types/program_loader.py rename to riscemu/core/program_loader.py diff --git a/riscemu/registers.py b/riscemu/core/registers.py similarity index 99% rename from riscemu/registers.py rename to riscemu/core/registers.py index 8808fdf..46dd596 100644 --- a/riscemu/registers.py +++ b/riscemu/core/registers.py @@ -7,9 +7,9 @@ from collections import defaultdict from typing import Union -from .helpers import * +from ..helpers import * -from .types import Int32, Float32 +from . import Int32, Float32 class Registers: diff --git a/riscemu/core/rtclock.py b/riscemu/core/rtclock.py new file mode 100644 index 0000000..6914e9c --- /dev/null +++ b/riscemu/core/rtclock.py @@ -0,0 +1,28 @@ +from time import time +from typing import Optional + +from . import UInt32 + + +class RTClock: + """ + Represents a realtime clock. Can be set up to start at a certain time t0, and + has a certain tickrate. + """ + + tickrate: int + t0: float + + def __init__(self, tickrate: int, t0: Optional[float] = None): + if t0 is None: + self.t0 = time() + self.tickrate = tickrate + + def get_low32(self, *args) -> UInt32: + return UInt32(int((time() - self.t0) * self.tickrate)) + + def get_hi32(self, *args) -> UInt32: + return UInt32(int((time() - self.t0) * self.tickrate) >> 32) + + def get_as_int(self): + return time() * self.tickrate diff --git a/riscemu/types/simple_instruction.py b/riscemu/core/simple_instruction.py similarity index 95% rename from riscemu/types/simple_instruction.py rename to riscemu/core/simple_instruction.py index 327ab1f..0de08e7 100644 --- a/riscemu/types/simple_instruction.py +++ b/riscemu/core/simple_instruction.py @@ -1,5 +1,6 @@ import re from typing import Union, Tuple +from functools import lru_cache from . import Instruction, T_RelativeAddress, InstructionContext, Immediate from ..helpers import parse_numeric_argument @@ -25,6 +26,7 @@ def __init__( def addr(self) -> int: return self._addr + self.context.base_address + @lru_cache(maxsize=None) def get_imm(self, num: int) -> Immediate: token = self.args[num] diff --git a/riscemu/priv/Exceptions.py b/riscemu/core/traps.py similarity index 89% rename from riscemu/priv/Exceptions.py rename to riscemu/core/traps.py index 3b039cd..f71fcf8 100644 --- a/riscemu/priv/Exceptions.py +++ b/riscemu/core/traps.py @@ -1,16 +1,10 @@ -from typing import Optional, NewType from enum import Enum -from .privmodes import PrivModes -from .CSRConsts import MCAUSE_TRANSLATION - -import typing - -from .. import RiscemuBaseException from ..colors import FMT_PARSE, FMT_NONE -from ..types import UInt32 - -if typing.TYPE_CHECKING: - from .ElfLoader import ElfInstruction +from .privmodes import PrivModes +from .csr_constants import MCAUSE_TRANSLATION +from .exceptions import RiscemuBaseException +from .int32 import UInt32 +from .instruction import InstructionWithEncoding class CpuTrapType(Enum): @@ -78,8 +72,8 @@ def __str__(self): class IllegalInstructionTrap(CpuTrap): - def __init__(self, ins: "ElfInstruction"): - super().__init__(2, ins.encoded, CpuTrapType.EXCEPTION) + def __init__(self, ins: InstructionWithEncoding): + super().__init__(2, ins.encoding, CpuTrapType.EXCEPTION) class InstructionAddressMisalignedTrap(CpuTrap): diff --git a/riscemu/CPU.py b/riscemu/core/usermode_cpu.py similarity index 79% rename from riscemu/CPU.py rename to riscemu/core/usermode_cpu.py index fc84cb0..8f1c83f 100644 --- a/riscemu/CPU.py +++ b/riscemu/core/usermode_cpu.py @@ -9,18 +9,22 @@ import typing from typing import List, Type -import riscemu -from .config import RunConfig -from .MMU import MMU -from .colors import FMT_CPU, FMT_NONE, FMT_ERROR -from .debug import launch_debug_session -from .types.exceptions import RiscemuBaseException, LaunchDebuggerException -from .syscall import SyscallInterface, get_syscall_symbols -from .types import CPU, ProgramLoader, Int32, BinaryDataMemorySection -from .parser import AssemblyFileLoader +from ..config import RunConfig +from ..colors import FMT_CPU, FMT_NONE, FMT_ERROR +from ..debug import launch_debug_session +from ..syscall import SyscallInterface, get_syscall_symbols +from . import ( + CPU, + Int32, + BinaryDataMemorySection, + MMU, + RiscemuBaseException, + LaunchDebuggerException, + PrivModes, +) if typing.TYPE_CHECKING: - from .instructions.instruction_set import InstructionSet + from ..instructions import InstructionSet class UserModeCPU(CPU): @@ -30,9 +34,7 @@ class UserModeCPU(CPU): It is initialized with a configuration and a list of instruction sets. """ - def __init__( - self, instruction_sets: List[Type["riscemu.InstructionSet"]], conf: RunConfig - ): + def __init__(self, instruction_sets: List[Type["InstructionSet"]], conf: RunConfig): """ Creates a CPU instance. @@ -50,19 +52,12 @@ def __init__( syscall_symbols = get_syscall_symbols() syscall_symbols.update(self.mmu.global_symbols) self.mmu.global_symbols.update(syscall_symbols) + self.mode = PrivModes.USER def step(self, verbose: bool = False): """ Execute a single instruction, then return. """ - if self.halted: - print( - FMT_CPU - + "[CPU] Program exited with code {}".format(self.exit_code) - + FMT_NONE - ) - return - launch_debugger = False try: @@ -132,7 +127,3 @@ def setup_stack(self, stack_size: int = 1024 * 4) -> bool: ) return True - - @classmethod - def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]: - return [AssemblyFileLoader] diff --git a/riscemu/debug.py b/riscemu/debug.py index 7d7a62f..4f43419 100644 --- a/riscemu/debug.py +++ b/riscemu/debug.py @@ -5,11 +5,11 @@ """ import os.path -from .types import SimpleInstruction +from .core import SimpleInstruction from .helpers import * if typing.TYPE_CHECKING: - from riscemu import CPU, Registers + pass HIST_FILE = os.path.join(os.path.expanduser("~"), ".riscemu_history") diff --git a/riscemu/helpers.py b/riscemu/helpers.py index 0545470..ee870ca 100644 --- a/riscemu/helpers.py +++ b/riscemu/helpers.py @@ -7,8 +7,8 @@ from math import log10, ceil from typing import Iterable, Iterator, TypeVar, Generic, List, Optional -from .types import Int32, UInt32 -from .types.exceptions import * +from .core import Int32, UInt32 +from .core.exceptions import * def align_addr(addr: int, to_bytes: int = 8) -> int: diff --git a/riscemu/instructions/RV32A.py b/riscemu/instructions/RV32A.py index 2d0ef4e..611e48b 100644 --- a/riscemu/instructions/RV32A.py +++ b/riscemu/instructions/RV32A.py @@ -1,6 +1,6 @@ from .instruction_set import InstructionSet, Instruction -from riscemu.types.exceptions import INS_NOT_IMPLEMENTED -from ..types import Int32, UInt32 +from riscemu.core.exceptions import INS_NOT_IMPLEMENTED +from ..core import Int32, UInt32 class RV32A(InstructionSet): @@ -19,60 +19,60 @@ def instruction_sc_w(self, ins: "Instruction"): def instruction_amoswap_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) if dest == "zero": - self.mmu.write(addr, val.to_bytes()) + self.mmu.write(addr.unsigned_value, val.to_bytes()) else: - old = Int32(self.mmu.read(addr, 4)) - self.mmu.write(addr, val.to_bytes()) + old = Int32(self.mmu.read(addr.unsigned_value, 4)) + self.mmu.write(addr.unsigned_value, val.to_bytes()) self.regs.set(dest, old) def instruction_amoadd_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) - old = Int32(self.mmu.read(addr, 4)) - self.mmu.write(addr, (old + val).to_bytes(4)) + old = Int32(self.mmu.read(addr.unsigned_value, 4)) + self.mmu.write(addr.unsigned_value, (old + val).to_bytes(4)) self.regs.set(dest, old) def instruction_amoand_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) - old = Int32(self.mmu.read(addr, 4)) - self.mmu.write(addr, (old & val).to_bytes(4)) + old = Int32(self.mmu.read(addr.unsigned_value, 4)) + self.mmu.write(addr.unsigned_value, (old & val).to_bytes(4)) self.regs.set(dest, old) def instruction_amoor_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) - old = Int32(self.mmu.read(addr, 4)) - self.mmu.write(addr, (old | val).to_bytes(4)) + old = Int32(self.mmu.read(addr.unsigned_value, 4)) + self.mmu.write(addr.unsigned_value, (old | val).to_bytes(4)) self.regs.set(dest, old) def instruction_amoxor_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) - old = Int32(self.mmu.read(addr, 4)) - self.mmu.write(addr, (old ^ val).to_bytes(4)) + old = Int32(self.mmu.read(addr.unsigned_value, 4)) + self.mmu.write(addr.unsigned_value, (old ^ val).to_bytes(4)) self.regs.set(dest, old) def instruction_amomax_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) - old = Int32(self.mmu.read(addr, 4)) - self.mmu.write(addr, max(old, val).to_bytes(4)) + old = Int32(self.mmu.read(addr.unsigned_value, 4)) + self.mmu.write(addr.unsigned_value, max(old, val).to_bytes(4)) self.regs.set(dest, old) def instruction_amomaxu_w(self, ins: "Instruction"): val: UInt32 - dest, addr, val = self.parse_rd_rs_rs(ins, signed=False) - old = UInt32(self.mmu.read(addr, 4)) + dest, addr, val = self.parse_rd_rs_rs(ins) + old = UInt32(self.mmu.read(addr.unsigned_value, 4)) - self.mmu.write(addr, max(old, val).to_bytes()) + self.mmu.write(addr.unsigned_value, max(old, val.unsigned()).to_bytes()) self.regs.set(dest, old) def instruction_amomin_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) - old = Int32(self.mmu.read(addr, 4)) - self.mmu.write(addr, min(old, val).to_bytes(4)) + old = Int32(self.mmu.read(addr.unsigned_value, 4)) + self.mmu.write(addr.unsigned_value, min(old, val).to_bytes(4)) self.regs.set(dest, old) def instruction_amominu_w(self, ins: "Instruction"): val: UInt32 - dest, addr, val = self.parse_rd_rs_rs(ins, signed=False) - old = UInt32(self.mmu.read(addr, 4)) + dest, addr, val = self.parse_rd_rs_rs(ins) + old = UInt32(self.mmu.read(addr.unsigned_value, 4)) - self.mmu.write(addr, min(old, val).to_bytes(4)) + self.mmu.write(addr.unsigned_value, min(old, val.unsigned()).to_bytes(4)) self.regs.set(dest, old) diff --git a/riscemu/instructions/RV32F.py b/riscemu/instructions/RV32F.py index 7699234..052b44c 100644 --- a/riscemu/instructions/RV32F.py +++ b/riscemu/instructions/RV32F.py @@ -11,7 +11,7 @@ from typing import Tuple from .instruction_set import InstructionSet, Instruction -from riscemu.types import INS_NOT_IMPLEMENTED, Float32, Int32, UInt32 +from riscemu.core import INS_NOT_IMPLEMENTED, Float32, Int32, UInt32 class RV32F(InstructionSet): diff --git a/riscemu/instructions/RV32I.py b/riscemu/instructions/RV32I.py index 066e69f..ea817df 100644 --- a/riscemu/instructions/RV32I.py +++ b/riscemu/instructions/RV32I.py @@ -4,13 +4,11 @@ SPDX-License-Identifier: MIT """ -from .instruction_set import * -from ..CPU import UserModeCPU +from .instruction_set import InstructionSet, ASSERT_LEN from ..colors import FMT_DEBUG, FMT_NONE -from riscemu.types.exceptions import LaunchDebuggerException from ..syscall import Syscall -from ..types import Instruction, Int32, UInt32 +from ..core import Instruction, Int32, UInt32, UserModeCPU, LaunchDebuggerException class RV32I(InstructionSet): @@ -121,7 +119,7 @@ def instruction_auipc(self, ins: "Instruction"): ASSERT_LEN(ins.args, 2) reg = ins.get_reg(0) imm = ins.get_imm(1).abs_value << 12 - self.regs.set(reg, imm + self.pc) + self.regs.set(reg, imm + self.cpu.pc) def instruction_xor(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) @@ -149,54 +147,54 @@ def instruction_andi(self, ins: "Instruction"): def instruction_slt(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set(rd, Int32(rs1 < rs2)) + self.regs.set(rd, UInt32(rs1 < rs2)) def instruction_slti(self, ins: "Instruction"): rd, rs1, imm = self.parse_rd_rs_imm(ins) - self.regs.set(rd, Int32(rs1 < imm)) + self.regs.set(rd, UInt32(rs1 < imm)) def instruction_sltu(self, ins: "Instruction"): - dst, rs1, rs2 = self.parse_rd_rs_rs(ins, signed=False) - self.regs.set(dst, Int32(rs1 < rs2)) + dst, rs1, rs2 = self.parse_rd_rs_rs(ins) + self.regs.set(dst, UInt32(rs1.unsigned_value < rs2.unsigned_value)) def instruction_sltiu(self, ins: "Instruction"): - dst, rs1, imm = self.parse_rd_rs_imm(ins, signed=False) - self.regs.set(dst, Int32(rs1 < imm)) + dst, rs1, imm = self.parse_rd_rs_imm(ins) + self.regs.set(dst, UInt32(rs1.unsigned_value < imm.abs_value.unsigned_value)) def instruction_beq(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 == rs2: - self.pc += dst.pcrel_value - 4 + self.cpu.pc += dst.pcrel_value.value - 4 def instruction_bne(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 != rs2: - self.pc += dst.pcrel_value - 4 + self.cpu.pc += dst.pcrel_value.value - 4 def instruction_blt(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 < rs2: - self.pc += dst.pcrel_value - 4 + self.cpu.pc += dst.pcrel_value.value - 4 def instruction_bge(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 >= rs2: - self.pc += dst.pcrel_value - 4 + self.cpu.pc += dst.pcrel_value.value - 4 def instruction_bltu(self, ins: "Instruction"): - rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) - if rs1 < rs2: - self.pc += dst.pcrel_value - 4 + rs1, rs2, dst = self.parse_rs_rs_imm(ins) + if rs1.unsigned_value < rs2.unsigned_value: + self.cpu.pc += dst.pcrel_value.value - 4 def instruction_bgeu(self, ins: "Instruction"): - rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) - if rs1 >= rs2: - self.pc += dst.pcrel_value - 4 + rs1, rs2, dst = self.parse_rs_rs_imm(ins) + if rs1.unsigned_value >= rs2.unsigned_value: + self.cpu.pc += dst.pcrel_value.value - 4 def instruction_j(self, ins: "Instruction"): ASSERT_LEN(ins.args, 1) addr = ins.get_imm(0) - self.pc += addr.pcrel_value - 4 + self.cpu.pc += addr.pcrel_value.value - 4 def instruction_jal(self, ins: "Instruction"): reg = "ra" # default register is ra @@ -206,20 +204,20 @@ def instruction_jal(self, ins: "Instruction"): ASSERT_LEN(ins.args, 2) reg = ins.get_reg(0) addr = ins.get_imm(1) - self.regs.set(reg, UInt32(self.pc)) - self.pc += addr.pcrel_value - 4 + self.regs.set(reg, UInt32(self.cpu.pc)) + self.cpu.pc += addr.pcrel_value.value - 4 def instruction_jalr(self, ins: "Instruction"): - ASSERT_LEN(ins.args, 2) + ASSERT_LEN(ins.args, 3) reg = ins.get_reg(0) base = ins.get_reg(1) - addr = ins.get_imm(2).abs_value - self.regs.set(reg, Int32(self.pc)) - self.pc = self.regs.get(base).unsigned_value + addr + addr = ins.get_imm(2).abs_value.value + self.regs.set(reg, Int32(self.cpu.pc)) + self.cpu.pc = self.regs.get(base).unsigned_value + addr def instruction_ret(self, ins: "Instruction"): ASSERT_LEN(ins.args, 0) - self.pc = self.regs.get("ra").unsigned_value + self.cpu.pc = self.regs.get("ra").unsigned_value def instruction_ecall(self, ins: "Instruction"): self.instruction_scall(ins) @@ -242,7 +240,7 @@ def instruction_sbreak(self, ins: "Instruction"): if self.cpu.conf.debug_instruction: print( FMT_DEBUG - + "Debug instruction encountered at 0x{:08X}".format(self.pc - 1) + + "Debug instruction encountered at 0x{:08X}".format(self.cpu.pc - 1) + FMT_NONE ) raise LaunchDebuggerException() diff --git a/riscemu/instructions/RV32M.py b/riscemu/instructions/RV32M.py index 0512ca4..42ec681 100644 --- a/riscemu/instructions/RV32M.py +++ b/riscemu/instructions/RV32M.py @@ -5,7 +5,7 @@ """ from .instruction_set import * -from riscemu.types.exceptions import INS_NOT_IMPLEMENTED +from riscemu.core.exceptions import INS_NOT_IMPLEMENTED class RV32M(InstructionSet): diff --git a/riscemu/instructions/RV_Debug.py b/riscemu/instructions/RV_Debug.py index efb143d..a898dcb 100644 --- a/riscemu/instructions/RV_Debug.py +++ b/riscemu/instructions/RV_Debug.py @@ -1,10 +1,21 @@ +from typing import Union + from .instruction_set import InstructionSet, Instruction class RV_Debug(InstructionSet): def instruction_print(self, ins: Instruction): reg = ins.get_reg(0) - print("register {} contains value {}".format(reg, self.regs.get(reg))) + if len(ins.args) == 2: + msg = ins.args[1] + print(msg.format(reg, self.regs.get(reg))) + else: + print("register {} contains value {}".format(reg, self.regs.get(reg))) + + def instruction_printf(self, ins: Instruction): + fmt_str = ins.args[0] + regs = tuple(self.smart_get_reg(x) for x in ins.args[1:]) + print(fmt_str.format(*regs)) def instruction_print_float(self, ins: Instruction): reg = ins.get_reg(0) @@ -31,3 +42,8 @@ def instruction_print_uhex(self, ins: Instruction): reg, hex(self.regs.get(reg).unsigned_value) ) ) + + def smart_get_reg(self, reg_name: str) -> Union[int, float]: + if reg_name[0] == "f": + return self.regs.get_f(reg_name).value + return self.regs.get(reg_name).value diff --git a/riscemu/instructions/Zicsr.py b/riscemu/instructions/Zicsr.py new file mode 100644 index 0000000..e9e27c7 --- /dev/null +++ b/riscemu/instructions/Zicsr.py @@ -0,0 +1,88 @@ +from typing import Tuple + +from .instruction_set import InstructionSet, Instruction +from ..core import Int32, UInt32 +from ..core.csr_constants import CSR_NAME_TO_ADDR +from ..helpers import parse_numeric_argument + + +class Zicsr(InstructionSet): + def instruction_csrrw(self, ins: Instruction): + rd, new_val, csr = self._parse_csr_ins(ins) + if rd != "zero": + self.regs.set(rd, self.cpu.csr.get(csr)) + self.cpu.csr.set(csr, new_val) + + def instruction_csrrwi(self, ins: Instruction): + rd, new_val, csr = self.parse_csr_imm_ins(ins) + if rd != "zero": + self.regs.set(rd, self.cpu.csr.get(csr)) + self.cpu.csr.set(csr, new_val) + + def instruction_csrrs(self, ins: Instruction): + rd, bitmask, csr = self._parse_csr_ins(ins) + val = self.cpu.csr.get(csr) + if rd != "zero": + self.regs.set(rd, val) + if bitmask != 0: + self.cpu.csr.set(csr, val | bitmask) + + def instruction_csrrsi(self, ins: Instruction): + rd, bitmask, csr = self.parse_csr_imm_ins(ins) + val = self.cpu.csr.get(csr) + if rd != "zero": + self.regs.set(rd, val) + if bitmask != 0: + self.cpu.csr.set(csr, val | bitmask) + + def instruction_csrrc(self, ins: Instruction): + rd, bitmask, csr = self._parse_csr_ins(ins) + val = self.cpu.csr.get(csr) + if rd != "zero": + self.regs.set(rd, val) + if bitmask != 0: + self.cpu.csr.set(csr, val & ~bitmask) + + def instruction_csrrci(self, ins: Instruction): + rd, bitmask, csr = self.parse_csr_imm_ins(ins) + val = self.cpu.csr.get(csr) + if rd != "zero": + self.regs.set(rd, val) + if bitmask != 0: + self.cpu.csr.set(csr, val & ~bitmask) + + def instruction_rdtime(self, ins: Instruction): + rd = ins.get_reg(0) + self.regs.set(rd, self.cpu.rtclock.get_low32()) + + def instruction_rdtimeh(self, ins: Instruction): + rd = ins.get_reg(0) + self.regs.set(rd, self.cpu.rtclock.get_hi32()) + + # FIXME: rdclycle[h] and rdinstret[h] are not provided yet + + def _parse_csr_ins(self, ins: Instruction) -> Tuple[str, UInt32, int]: + assert len(ins.args) == 3 + rd = ins.get_reg(0) + rs = self.regs.get(ins.get_reg(1)).unsigned() + + return rd, rs, self._ins_get_csr_addr(ins) + + def parse_csr_imm_ins(self, ins: Instruction) -> Tuple[str, UInt32, int]: + assert len(ins.args) == 3 + rd = ins.get_reg(0) + # make sure we only have 5 bit immediate values here + rs = ins.get_imm(1).abs_value.unsigned() + if rs > 0b11111: + print(f"Warning! more than 5bit immediate value in csr ins {ins}!") + rs = rs & 0b11111 + + return rd, rs, self._ins_get_csr_addr(ins) + + @staticmethod + def _ins_get_csr_addr(ins: Instruction) -> int: + # TODO: somehow handle elf instructions at some point? + if isinstance(ins.args[2], str) and ins.args[2].lower() in CSR_NAME_TO_ADDR: + return CSR_NAME_TO_ADDR[ins.args[2].lower()] + + return ins.get_imm(2).abs_value.unsigned_value diff --git a/riscemu/instructions/__init__.py b/riscemu/instructions/__init__.py index 510ef2a..dce83fd 100644 --- a/riscemu/instructions/__init__.py +++ b/riscemu/instructions/__init__.py @@ -12,5 +12,8 @@ from .RV32A import RV32A from .RV32F import RV32F from .RV_Debug import RV_Debug +from .Zicsr import Zicsr -InstructionSetDict = {v.__name__: v for v in [RV32I, RV32M, RV32A, RV32F, RV_Debug]} +InstructionSetDict = { + v.__name__: v for v in [RV32I, RV32M, RV32A, RV32F, Zicsr, RV_Debug] +} diff --git a/riscemu/instructions/instruction_set.py b/riscemu/instructions/instruction_set.py index a970ad1..ec28988 100644 --- a/riscemu/instructions/instruction_set.py +++ b/riscemu/instructions/instruction_set.py @@ -8,9 +8,8 @@ from abc import ABC -from ..CPU import CPU -from riscemu.types.exceptions import ASSERT_LEN -from ..types import Instruction, Int32, UInt32, Immediate +from ..core.exceptions import ASSERT_LEN +from ..core import Instruction, Int32, UInt32, Immediate, CPU class InstructionSet(ABC): @@ -58,7 +57,7 @@ def parse_mem_ins(self, ins: "Instruction") -> Tuple[str, UInt32]: assert len(ins.args) == 3 # handle rd, rs1, imm rd = ins.get_reg(0) - rs = self.get_reg_content(ins, 1).unsigned() + rs = self.regs.get(ins.get_reg(1)).unsigned() imm = ins.get_imm(2) return rd, rs + imm.abs_value.unsigned() @@ -73,56 +72,37 @@ def parse_rd_rs_rs( if signed: return ( ins.get_reg(0), - Int32(self.get_reg_content(ins, 1)), - Int32(self.get_reg_content(ins, 2)), + Int32(self.regs.get(ins.get_reg(1))), + Int32(self.regs.get(ins.get_reg(2))), ) else: return ( ins.get_reg(0), - UInt32(self.get_reg_content(ins, 1)), - UInt32(self.get_reg_content(ins, 2)), + UInt32(self.regs.get(ins.get_reg(1))), + UInt32(self.regs.get(ins.get_reg(2))), ) - def parse_rd_rs_imm( - self, ins: "Instruction", signed=True - ) -> Tuple[str, Int32, Immediate]: + def parse_rd_rs_imm(self, ins: "Instruction") -> Tuple[str, Int32, Immediate]: """ Assumes the command is in rd, rs, imm format Returns the name of rd, the value in rs and the immediate imm """ - ASSERT_LEN(ins.args, 3) - if signed: - return ( - ins.get_reg(0), - Int32(self.get_reg_content(ins, 1)), - ins.get_imm(2), - ) - else: - return ( - ins.get_reg(0), - UInt32(self.get_reg_content(ins, 1)), - ins.get_imm(2), - ) + return ( + ins.get_reg(0), + Int32(self.regs.get(ins.get_reg(1))), + ins.get_imm(2), + ) - def parse_rs_rs_imm( - self, ins: "Instruction", signed=True - ) -> Tuple[Int32, Int32, Immediate]: + def parse_rs_rs_imm(self, ins: "Instruction") -> Tuple[Int32, Int32, Immediate]: """ Assumes the command is in rs1, rs2, imm format Returns the values in rs1, rs2 and the immediate imm """ - if signed: - return ( - Int32(self.get_reg_content(ins, 0)), - Int32(self.get_reg_content(ins, 1)), - ins.get_imm(2), - ) - else: - return ( - UInt32(self.get_reg_content(ins, 0)), - UInt32(self.get_reg_content(ins, 1)), - ins.get_imm(2), - ) + return ( + Int32(self.regs.get(ins.get_reg(0))), + Int32(self.regs.get(ins.get_reg(1))), + ins.get_imm(2), + ) def get_reg_content(self, ins: "Instruction", ind: int) -> Int32: """ diff --git a/riscemu/interactive.py b/riscemu/interactive.py index 038ffb7..43ebff4 100644 --- a/riscemu/interactive.py +++ b/riscemu/interactive.py @@ -1,12 +1,11 @@ import sys -from riscemu import RunConfig -from riscemu.types import InstructionMemorySection, SimpleInstruction, Program +from riscemu.config import RunConfig +from riscemu.core import InstructionMemorySection, SimpleInstruction, Program if __name__ == "__main__": - from .CPU import UserModeCPU + from core.usermode_cpu import UserModeCPU from .instructions import InstructionSetDict - from .debug import launch_debug_session cpu = UserModeCPU(list(InstructionSetDict.values()), RunConfig(verbosity=4)) diff --git a/riscemu/parser.py b/riscemu/parser.py index f907b05..f029385 100644 --- a/riscemu/parser.py +++ b/riscemu/parser.py @@ -11,8 +11,13 @@ from .colors import FMT_PARSE from .helpers import Peekable from .tokenizer import Token, TokenType, tokenize -from .types import Program, T_ParserOpts, ProgramLoader, SimpleInstruction -from .types.exceptions import ParseException +from .core import ( + Program, + T_ParserOpts, + ProgramLoader, + SimpleInstruction, + ParseException, +) def parse_instruction(token: Token, args: Tuple[str], context: ParseContext): diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index a9fe7be..1c17e87 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -1,11 +1,11 @@ from typing import Dict, Union, Callable, Optional from collections import defaultdict -from .privmodes import PrivModes -from .Exceptions import InstructionAccessFault +from core.privmodes import PrivModes +from core.traps import InstructionAccessFault from ..colors import FMT_CSR, FMT_NONE -from .CSRConsts import CSR_NAME_TO_ADDR, MSTATUS_LEN_2, MSTATUS_OFFSETS -from ..types import UInt32 +from core.csr_constants import CSR_NAME_TO_ADDR, MSTATUS_LEN_2, MSTATUS_OFFSETS +from ..core import UInt32 class CSR: diff --git a/riscemu/priv/ElfLoader.py b/riscemu/priv/ElfLoader.py index 030b9de..91cb9ea 100644 --- a/riscemu/priv/ElfLoader.py +++ b/riscemu/priv/ElfLoader.py @@ -2,10 +2,10 @@ from io import IOBase, RawIOBase from typing import List -from .Exceptions import * +from core.traps import * from .types import ElfMemorySection from ..helpers import FMT_PARSE, FMT_NONE, FMT_GREEN, FMT_BOLD -from ..types import MemoryFlags, Program, ProgramLoader, T_ParserOpts +from ..core import MemoryFlags, Program, ProgramLoader, T_ParserOpts FMT_ELF = FMT_GREEN + FMT_BOLD @@ -94,8 +94,6 @@ def _lms_from_elf_sec(self, sec: "Section", owner: str): ) def _parse_symtab(self, symtab: "SymbolTableSection"): - from elftools.elf.enums import ENUM_ST_VISIBILITY - for sym in symtab.iter_symbols(): if not sym.name: continue diff --git a/riscemu/priv/ImageLoader.py b/riscemu/priv/ImageLoader.py index feda1d8..7eb2102 100644 --- a/riscemu/priv/ImageLoader.py +++ b/riscemu/priv/ImageLoader.py @@ -8,8 +8,9 @@ from .ElfLoader import ElfMemorySection from .types import MemoryImageDebugInfos +from ..assembler import INSTRUCTION_SECTION_NAMES from ..colors import FMT_NONE, FMT_PARSE -from ..types import MemoryFlags, ProgramLoader, Program, T_ParserOpts +from ..core import MemoryFlags, ProgramLoader, Program, T_ParserOpts class MemoryImageLoader(ProgramLoader): diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 3c36b27..afaeeb4 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -6,20 +6,20 @@ import sys import time -from riscemu.CPU import * +from core.usermode_cpu import * from .CSR import CSR from .ElfLoader import ElfBinaryFileLoader -from .Exceptions import * +from core.traps import * from .ImageLoader import MemoryImageLoader from .PrivMMU import PrivMMU from .PrivRV32I import PrivRV32I -from .privmodes import PrivModes +from core.privmodes import PrivModes from ..IO.TextIO import TextIO from ..instructions import RV32A, RV32M -from ..types import Program, UInt32 +from ..core import Program, UInt32 if typing.TYPE_CHECKING: - from riscemu.instructions.instruction_set import InstructionSet + pass class PrivCPU(CPU): @@ -270,7 +270,3 @@ def show_perf(self): def record_perf_profile(self): self._perf_counters.append((time.perf_counter_ns(), self.cycle)) - - @classmethod - def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]: - return [AssemblyFileLoader, MemoryImageLoader, ElfBinaryFileLoader] diff --git a/riscemu/priv/PrivMMU.py b/riscemu/priv/PrivMMU.py index 16f54d8..b763055 100644 --- a/riscemu/priv/PrivMMU.py +++ b/riscemu/priv/PrivMMU.py @@ -1,11 +1,10 @@ from .types import ElfMemorySection -from ..MMU import * -from abc import abstractmethod +from core.mmu import * import typing if typing.TYPE_CHECKING: - from .PrivCPU import PrivCPU + pass class PrivMMU(MMU): @@ -23,7 +22,7 @@ def get_sec_containing(self, addr: T_AbsoluteAddress) -> MemorySection: # get sec succeeding empty space at addr sec_after = next((sec for sec in self.sections if sec.base > addr), None) - # calc start end end of "free" space + # calc start and end of "free" space prev_sec_end = 0 if sec_before is None else sec_before.end next_sec_start = 0x7FFFFFFF if sec_after is None else sec_after.base diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index 7a3df9e..bbf19c7 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -5,9 +5,9 @@ """ from ..instructions.RV32I import * -from riscemu.types.exceptions import INS_NOT_IMPLEMENTED -from .Exceptions import * -from .privmodes import PrivModes +from riscemu.core.exceptions import INS_NOT_IMPLEMENTED +from riscemu.core.traps import * +from riscemu.core.privmodes import PrivModes from ..colors import FMT_CPU, FMT_NONE import typing @@ -123,13 +123,13 @@ def instruction_bge(self, ins: "Instruction"): self.pc += dst.abs_value - 4 def instruction_bltu(self, ins: "Instruction"): - rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) - if rs1 < rs2: + rs1, rs2, dst = self.parse_rs_rs_imm(ins) + if rs1.unsigned_value < rs2.unsigned_value: self.pc += dst.abs_value - 4 def instruction_bgeu(self, ins: "Instruction"): - rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) - if rs1 >= rs2: + rs1, rs2, dst = self.parse_rs_rs_imm(ins) + if rs1.unsigned_value >= rs2.unsigned_value: self.pc += dst.abs_value - 4 # technically deprecated @@ -172,6 +172,6 @@ def parse_crs_ins(self, ins: "Instruction"): def parse_mem_ins(self, ins: "Instruction") -> Tuple[str, int]: ASSERT_LEN(ins.args, 3) - addr = self.get_reg_content(ins, 1) + ins.get_imm(2).abs_value + addr = self.regs.get(ins.get_reg(1)) + ins.get_imm(2).abs_value reg = ins.get_reg(0) return reg, addr diff --git a/riscemu/priv/__main__.py b/riscemu/priv/__main__.py index aac301b..496fca2 100644 --- a/riscemu/priv/__main__.py +++ b/riscemu/priv/__main__.py @@ -1,5 +1,5 @@ from riscemu import RunConfig -from riscemu.types import Program +from riscemu.core import Program from .PrivCPU import PrivCPU import sys diff --git a/riscemu/priv/types.py b/riscemu/priv/types.py index 7ecde55..654ab21 100644 --- a/riscemu/priv/types.py +++ b/riscemu/priv/types.py @@ -6,12 +6,12 @@ from riscemu.colors import FMT_NONE, FMT_PARSE from riscemu.decoder import format_ins, RISCV_REGS, decode -from riscemu.priv.Exceptions import ( +from riscemu.core.traps import ( InstructionAccessFault, InstructionAddressMisalignedTrap, LoadAccessFault, ) -from riscemu.types import ( +from riscemu.core import ( Instruction, InstructionContext, T_RelativeAddress, diff --git a/riscemu/riscemu_main.py b/riscemu/riscemu_main.py index b24a7fa..b3172b2 100644 --- a/riscemu/riscemu_main.py +++ b/riscemu/riscemu_main.py @@ -6,12 +6,12 @@ from io import IOBase, RawIOBase, TextIOBase from typing import Type, Dict, List, Optional, Union -from riscemu import AssemblyFileLoader, __version__, __copyright__ -from riscemu.colors import FMT_GRAY, FMT_NONE -from riscemu.types import CPU, ProgramLoader, Program -from riscemu.instructions import InstructionSet, InstructionSetDict -from riscemu.config import RunConfig -from riscemu.CPU import UserModeCPU +from . import __version__, __copyright__ +from .core import CPU, ProgramLoader, Program, UserModeCPU +from .instructions import InstructionSet, InstructionSetDict +from .config import RunConfig +from .helpers import FMT_GRAY, FMT_NONE +from .parser import AssemblyFileLoader @dataclass diff --git a/riscemu/syscall.py b/riscemu/syscall.py index 99764ad..eccec24 100644 --- a/riscemu/syscall.py +++ b/riscemu/syscall.py @@ -9,10 +9,14 @@ from math import log2, ceil from typing import Dict, IO, Union -from .types import BinaryDataMemorySection, MemoryFlags +from .core import ( + BinaryDataMemorySection, + MemoryFlags, + Int32, + CPU, + InvalidSyscallException, +) from .colors import FMT_SYSCALL, FMT_NONE -from .types import Int32, CPU -from .types.exceptions import InvalidSyscallException SYSCALLS = { 63: "read", diff --git a/riscemu/tokenizer.py b/riscemu/tokenizer.py index fdeffb4..7b7e6fa 100644 --- a/riscemu/tokenizer.py +++ b/riscemu/tokenizer.py @@ -10,7 +10,7 @@ from typing import List, Iterable from riscemu.decoder import RISCV_REGS -from riscemu.types.exceptions import ParseException +from riscemu.core.exceptions import ParseException LINE_COMMENT_STARTERS = ("#", ";", "//") WHITESPACE_PATTERN = re.compile(r"\s+") diff --git a/snitch/regs.py b/snitch/regs.py index 333d9d4..0ec6f28 100644 --- a/snitch/regs.py +++ b/snitch/regs.py @@ -1,14 +1,10 @@ from typing import Dict, List, Tuple -from riscemu.registers import Registers -from riscemu.MMU import MMU -from riscemu.types import Int32, UInt32 +from riscemu.core import Registers, MMU, Float32 from dataclasses import dataclass from enum import Enum -from riscemu.types.float32 import Float32 - class StreamMode(Enum): READ = 1 diff --git a/test/filecheck/csr.asm b/test/filecheck/csr.asm new file mode 100644 index 0000000..6cf342c --- /dev/null +++ b/test/filecheck/csr.asm @@ -0,0 +1,37 @@ +.text + +.globl main +main: +// check that less that 1000 ticks passed since start + csrrs a0, zero, time + li a1, 1000 + bge a0, a1, fail + print a0, "time passed since launch: {1} ticks" +// CHECK: time passed since launch: +// check that timeh is empty + csrrs a0, zero, timeh + bne a0, zero, fail +// check that some ammount of cycles has passed so far: + csrrs a0, zero, cycle + beq a0, zero, fail + print a0, "cycles passed: {1}" +// CHECK-NEXT: cycles passed: +// check that cycleh is zero + csrrs a0, zero, cycleh + bne a0, zero, fail +// check instret and instreth + csrrs a0, zero, instret + beq a0, zero, fail + print a0, "instret is: {1}" +// CHECK-NEXT: instret is: + csrrs a0, zero, instreth + bne a0, zero, fail +// CHECK-NEXT: Success! + print a0, "Success!" + ret + +fail: + li a0, -1 + print a0, "Failure!" + +ret diff --git a/test/test_RV32F.py b/test/test_RV32F.py index 69f675f..d5d758d 100644 --- a/test/test_RV32F.py +++ b/test/test_RV32F.py @@ -1,11 +1,10 @@ from typing import Iterable -from riscemu import CPU from riscemu.instructions.RV32F import RV32F -from riscemu.registers import Registers -from riscemu.types import ProgramLoader -from riscemu.types.float32 import Float32 -from riscemu.types.int32 import Int32 -from riscemu.types.simple_instruction import SimpleInstruction +from riscemu.core.registers import Registers +from riscemu.core import ProgramLoader, CPU +from riscemu.core.float32 import Float32 +from riscemu.core.int32 import Int32 +from riscemu.core.simple_instruction import SimpleInstruction class MockInstruction(SimpleInstruction): diff --git a/test/test_float32.py b/test/test_float32.py index a045302..d592acc 100644 --- a/test/test_float32.py +++ b/test/test_float32.py @@ -1,6 +1,6 @@ import math -from riscemu.types import Float32 +from riscemu.core import Float32 # pi encoded as a 32bit little endian float PI_BYTES_LE = b"\xdb\x0fI@" diff --git a/test/test_integers.py b/test/test_integers.py index 8c61201..57b7bed 100644 --- a/test/test_integers.py +++ b/test/test_integers.py @@ -1,4 +1,4 @@ -from riscemu.types import Int32, UInt32 +from riscemu.core import Int32, UInt32 def test_logical_right_shift(): diff --git a/test/test_isa.py b/test/test_isa.py index 2fdd455..60137be 100644 --- a/test/test_isa.py +++ b/test/test_isa.py @@ -1,6 +1,6 @@ from riscemu.colors import FMT_ERROR, FMT_NONE, FMT_BOLD, FMT_GREEN from riscemu.instructions import InstructionSet -from riscemu.types import Instruction, CPU +from riscemu.core import Instruction, CPU from riscemu.decoder import RISCV_REGS FMT_SUCCESS = FMT_GREEN + FMT_BOLD diff --git a/test/test_mstatus.py b/test/test_mstatus.py new file mode 100644 index 0000000..294c995 --- /dev/null +++ b/test/test_mstatus.py @@ -0,0 +1,25 @@ +from riscemu.core.csr import MStatusRegister + + +def test_mstatus_bits(): + status = MStatusRegister() + + status.mpie = 1 + + assert "{:032b}".format(int(status.state)) == "00000000000000000000000010000000" + + status.mpie = 0 + + assert "{:032b}".format(int(status.state)) == "00000000000000000000000000000000" + + status.mpp = 3 + + assert "{:032b}".format(int(status.state)) == "00000000000000000001100000000000" + + status.sd = 1 + + assert "{:032b}".format(int(status.state)) == "10000000000000000001100000000000" + + status.mpp = 1 + + assert "{:032b}".format(int(status.state)) == "10000000000000000000100000000000" diff --git a/test/test_regs.py b/test/test_regs.py index be5bfea..b977ae0 100644 --- a/test/test_regs.py +++ b/test/test_regs.py @@ -1,7 +1,7 @@ import pytest -from riscemu.registers import Registers -from riscemu.types import Float32 +from riscemu.core.registers import Registers +from riscemu.core import Float32 def test_float_regs():