From a2e206eaee99a1a25b9e4c175fec7f6fc4cf15fb Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Wed, 19 May 2021 09:51:51 +0200 Subject: [PATCH 01/69] renamed CPU.__run -> CPU._run, it's now overwriteable by subclasses --- riscemu/CPU.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/riscemu/CPU.py b/riscemu/CPU.py index a51103d..f116a4e 100644 --- a/riscemu/CPU.py +++ b/riscemu/CPU.py @@ -94,7 +94,7 @@ class CPU: print(FMT_CPU + '[CPU] Allocated {} bytes of stack'.format(self.stack.size) + FMT_NONE) print(FMT_CPU + '[CPU] Started running from 0x{:08X} ({})'.format(le.run_ptr, le.name) + FMT_NONE) - self.__run() + self._run() def continue_from_debugger(self, verbose=True): """ @@ -102,7 +102,7 @@ class CPU: :param verbose: If True, will print each executed instruction to STDOUT """ - self.__run(verbose) + self._run(verbose) def step(self): """ @@ -123,7 +123,7 @@ class CPU: self.pc -= 1 print(ex.message()) - def __run(self, verbose=False): + def _run(self, verbose=False): if self.pc <= 0: return False ins = None -- 2.40.1 From 483a3f24163341853503151a85596d2651f10e64 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Wed, 19 May 2021 12:14:43 +0200 Subject: [PATCH 02/69] Priv: [wip] implementing privileged architecture --- riscemu/priv/CSR.py | 101 +++++++++++++++++++++++++++++++ riscemu/priv/ElfLoader.py | 9 +++ riscemu/priv/Exceptions.py | 23 +++++++ riscemu/priv/PrivCPU.py | 121 +++++++++++++++++++++++++++++++++++++ riscemu/priv/PrivRV32I.py | 79 ++++++++++++++++++++++++ riscemu/priv/__init__.py | 15 +++++ riscemu/priv/__main__.py | 27 +++++++++ 7 files changed, 375 insertions(+) create mode 100644 riscemu/priv/CSR.py create mode 100644 riscemu/priv/ElfLoader.py create mode 100644 riscemu/priv/Exceptions.py create mode 100644 riscemu/priv/PrivCPU.py create mode 100644 riscemu/priv/PrivRV32I.py create mode 100644 riscemu/priv/__init__.py create mode 100644 riscemu/priv/__main__.py diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py new file mode 100644 index 0000000..35e7277 --- /dev/null +++ b/riscemu/priv/CSR.py @@ -0,0 +1,101 @@ +from typing import Dict, Union +from collections import defaultdict + +MSTATUS_OFFSETS = { + '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 +""" + + +class CSR: + """ + This holds all Control and Status Registers (CSR) + """ + regs: Dict[int, int] + """ + All Control and Status Registers are stored here + """ + + name_to_addr: Dict[str, int] = { + 'mstatus': 0x300, + 'misa': 0x301, + 'mie': 0x304, + 'mtvec': 0x305, + 'mepc': 0x341, + 'mcause': 0x342, + 'mtval': 0x343, + 'mip': 0x344, + 'mvendorid': 0xF11, + 'marchid': 0xF12, + 'mimpid': 0xF13, + 'mhartid': 0xF14, + } + """ + Translation for named registers + """ + + def __init__(self): + self.regs = defaultdict(lambda: 0) + + def set(self, addr: Union[str, int], val: int): + if isinstance(addr, str): + if not addr in self.name_to_addr: + print("Unknown CSR register {}".format(addr)) + addr = self.name_to_addr[addr] + self.regs[addr] = val + + def get(self, addr: Union[str, int]): + if isinstance(addr, str): + if not addr in self.name_to_addr: + print("Unknown CSR register {}".format(addr)) + addr = self.name_to_addr[addr] + return self.regs[addr] + + # mstatus properties + def set_mstatus(self, name: str, val: int): + """ + Set mstatus bits using this helper. mstatus is a 32 bit register, holding various machine status flags + Setting them by hand is super painful, so this helper allows you to set specific bits. + + Please make sure your supplied value has the correct width! + + :param name: + :param val: + :return: + """ + size = 2 if name in MSTATUS_LEN_2 else 1 + off = MSTATUS_OFFSETS[name] + mask = (2**size - 1) << off + old_val = self.get('mstatus') + erased = old_val & (~mask) + new_val = erased | (val << off) + self.set('mstatus', new_val) + + def get_mstatus(self, name): + size = 2 if name in MSTATUS_LEN_2 else 1 + off = MSTATUS_OFFSETS[name] + mask = (2**size - 1) << off + return (self.get('mstatus') & mask) >> off diff --git a/riscemu/priv/ElfLoader.py b/riscemu/priv/ElfLoader.py new file mode 100644 index 0000000..d61446b --- /dev/null +++ b/riscemu/priv/ElfLoader.py @@ -0,0 +1,9 @@ +from ..Executable import Executable, MemorySection, MemoryFlags + +# This requires pyelftools package! + +class ElfExecutable(Executable): + def __init__(self, name): + from elftools.elf.elffile import ELFFile + + with open(f) diff --git a/riscemu/priv/Exceptions.py b/riscemu/priv/Exceptions.py new file mode 100644 index 0000000..6a2eafa --- /dev/null +++ b/riscemu/priv/Exceptions.py @@ -0,0 +1,23 @@ +from typing import Optional + + +class CpuTrap(BaseException): + code: int + interrupt: int + mtval: int + + def __init__(self, interrupt: int, code: int, mtval=0): + assert 0 <= interrupt <= 1 + + self.interrupt = interrupt + self.code = code + self.mtval = mtval + + @property + def mcause(self): + return (self.code << 31) + self.interrupt + + +class IllegalInstructionTrap(CpuTrap): + def __init__(self): + super().__init__(0, 2, 0) diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py new file mode 100644 index 0000000..b0e2a63 --- /dev/null +++ b/riscemu/priv/PrivCPU.py @@ -0,0 +1,121 @@ +""" +RiscEmu (c) 2021 Anton Lydike + +SPDX-License-Identifier: MIT +""" + +from riscemu.CPU import * +from enum import IntEnum +from riscemu.Executable import LoadedMemorySection +from .Exceptions import * +from .CSR import CSR +from .PrivRV32I import PrivRV32I +from ..instructions.RV32M import RV32M + +from collections import defaultdict + +from typing import Union + +if typing.TYPE_CHECKING: + from riscemu import Executable, LoadedExecutable, LoadedInstruction + from riscemu.instructions.InstructionSet import InstructionSet + + +class PrivModes(IntEnum): + USER = 0 + SUPER = 1 + MACHINE = 3 + + +class PrivCPU(CPU): + """ + This is a CPU that has different modes, instruction sets and registers. + + It should support M and U Mode, but no U-Mode Traps. + + This allows us to + """ + + csr: CSR + + INS_XLEN = 1 + """ + Size of an instruction in memory. Should be 4, but since our loading code is shit, instruction take up + the equivalent of "1 byte" (this is actually impossible) + """ + + def __init__(self, conf): + super().__init__(conf, [PrivRV32I, RV32M]) + self.mode: PrivModes = PrivModes.MACHINE + + # set up CSR + self.csr = CSR() + # TODO: Actually populate the CSR with real data (vendorID, heartID, machine implementation etc) + self.csr.set('mhartid', 0) # core id + self.csr.set('mimpid', 1) # implementation id + self.csr.set('misa', ()) # available ISA + + def _run(self, verbose=False): + if self.pc <= 0: + return False + ins = None + try: + while not self.exit: + try: + 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 += 1 + self.run_instruction(ins) + except CpuTrap as trap: + mie = self.csr.get_mstatus('mie') + if not mie: + print("Caught trap while mie={}!".format(mie)) + # TODO: handle this a lot better + continue + # raise trap + + # caught a trap! + self.csr.set('mepc', self.pc) # store MEPC + self.csr.set_mstatus('mpp', self.mode) # save mpp + self.csr.set_mstatus('mpie', mie) # save mie + self.csr.set_mstatus('mie', 0) # disable further interrupts + self.csr.set('mcause', trap.mcause) # store cause + + # set mtval csr + self.csr.set('mtval', trap.mtval) + + # set priv mode to machine + self.mode = PrivModes.MACHINE + + # trap vector + mtvec = self.csr.get('mtvec') + if mtvec & 3 == 1: + # vectored mode! + self.pc = (mtvec >> 2) + (self.INS_XLEN * trap.code) + else: + # standard mode + self.pc = (mtvec >> 2) + + + 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) + print(ex.message()) + self.pc -= 1 + + 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) diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py new file mode 100644 index 0000000..dc56df9 --- /dev/null +++ b/riscemu/priv/PrivRV32I.py @@ -0,0 +1,79 @@ +""" +RiscEmu (c) 2021 Anton Lydike + +SPDX-License-Identifier: MIT +""" + +from riscemu.instructions.RV32I import * +from ..Exceptions import INS_NOT_IMPLEMENTED +from riscemu.priv.PrivCPU import PrivModes, PrivCPU +from .Exceptions import * + + +class PrivRV32I(RV32I): + cpu: PrivCPU + """ + This is an extension of RV32I, written for the PrivCPU class + """ + + def instruction_csrrw(self, ins: 'LoadedInstruction'): + rd, off, rs = self.parse_crs_ins(ins) + if rd != 'zero': + old_val = int_from_bytes(self.cpu.csr.read(off, 4)) + self.regs.set(rd, old_val) + self.cpu.csr.write(off, 4, int_to_bytes(rs)) + + def instruction_csrrs(self, ins: 'LoadedInstruction'): + INS_NOT_IMPLEMENTED(ins) + + def instruction_csrrc(self, ins: 'LoadedInstruction'): + INS_NOT_IMPLEMENTED(ins) + + def instruction_csrrsi(self, ins: 'LoadedInstruction'): + INS_NOT_IMPLEMENTED(ins) + + def instruction_csrrwi(self, ins: 'LoadedInstruction'): + INS_NOT_IMPLEMENTED(ins) + + def instruction_csrrci(self, ins: 'LoadedInstruction'): + INS_NOT_IMPLEMENTED(ins) + + def instruction_mret(self, ins: 'LoadedInstruction'): + if self.cpu.mode != PrivModes.MACHINE: + print("MRET not inside machine level code!") + raise IllegalInstructionTrap() + # retore mie + mpie = self.cpu.csr.get_mstatus('mpie') + self.cpu.csr.set_mstatus('mie', mpie) + # restore priv + mpp = self.cpu.csr.get_mstatus('mpp') + self.cpu.mode = PrivModes(mpp) + # restore pc + mepc = self.cpu.csr.get('mepc') + self.cpu.pc = mepc + + def instruction_uret(self, ins: 'LoadedInstruction'): + raise IllegalInstructionTrap() + + def instruction_sret(self, ins: 'LoadedInstruction'): + raise IllegalInstructionTrap() + + def instruction_scall(self, ins: 'LoadedInstruction'): + """ + Overwrite the scall from userspace RV32I + """ + if self.cpu.mode == PrivModes.USER: + raise CpuTrap(0, 8) # ecall from U mode + elif self.cpu.mode == PrivModes.SUPER: + raise CpuTrap(0, 9) # ecall from S mode - should not happen + elif self.cpu.mode == PrivModes.MACHINE: + raise CpuTrap(0, 11) # ecall from M mode + + + + + + + def parse_crs_ins(self, ins: 'LoadedInstruction'): + ASSERT_LEN(ins.args, 3) + return ins.get_reg(0), ins.get_imm(1), self.get_reg_content(ins, 2) diff --git a/riscemu/priv/__init__.py b/riscemu/priv/__init__.py new file mode 100644 index 0000000..26e488f --- /dev/null +++ b/riscemu/priv/__init__.py @@ -0,0 +1,15 @@ +""" +RiscEmu (c) 2021 Anton Lydike + +SPDX-License-Identifier: MIT + +The priv Module holds everything necessary for emulating privileged risc-v assembly + + +Running priv is only preferable to the normal userspace emulator, if you actually want to emulate the whole system. + +Syscalls will have to be intercepted by your assembly code. + + +The PrivCPU Implements the Risc-V M/U Model, meaning there is machine mode and user mode. No PMP or paging is available. +""" \ No newline at end of file diff --git a/riscemu/priv/__main__.py b/riscemu/priv/__main__.py new file mode 100644 index 0000000..dd38576 --- /dev/null +++ b/riscemu/priv/__main__.py @@ -0,0 +1,27 @@ +from .PrivCPU import * + +from ..Tokenizer import RiscVInput +from ..ExecutableParser import ExecutableParser + +import sys + +if __name__ == '__main__': + + files = sys.argv + + cpu = PrivCPU(RunConfig()) + + try: + loaded_exe = None + for file in files: + tk = cpu.get_tokenizer(RiscVInput.from_file(file)) + tk.tokenize() + loaded_exe = cpu.load(ExecutableParser(tk).parse()) + # run the last loaded executable + cpu.run_loaded(loaded_exe) + except RiscemuBaseException as e: + print("Error while parsing: {}".format(e.message())) + import traceback + + traceback.print_exception(type(e), e, e.__traceback__) + sys.exit(1) -- 2.40.1 From a4735db388c669d91f443b0ba0125ac66aa540ea Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sat, 22 May 2021 21:01:03 +0200 Subject: [PATCH 03/69] Added a decoder module which can deocde some RV32I/M instructions Some of them even correctly O.o --- riscemu/decoder/__init__.py | 1 + riscemu/decoder/__main__.py | 17 ++++ riscemu/decoder/decoder.py | 86 ++++++++++++++++++++ riscemu/decoder/formats.py | 114 +++++++++++++++++++++++++++ riscemu/decoder/instruction_table.py | 69 ++++++++++++++++ riscemu/decoder/regs.py | 6 ++ riscemu/priv/PrivMMU.py | 0 riscemu/priv/privmodes.py | 0 8 files changed, 293 insertions(+) create mode 100644 riscemu/decoder/__init__.py create mode 100644 riscemu/decoder/__main__.py create mode 100644 riscemu/decoder/decoder.py create mode 100644 riscemu/decoder/formats.py create mode 100644 riscemu/decoder/instruction_table.py create mode 100644 riscemu/decoder/regs.py create mode 100644 riscemu/priv/PrivMMU.py create mode 100644 riscemu/priv/privmodes.py diff --git a/riscemu/decoder/__init__.py b/riscemu/decoder/__init__.py new file mode 100644 index 0000000..3ba852e --- /dev/null +++ b/riscemu/decoder/__init__.py @@ -0,0 +1 @@ +from .decoder import decode \ No newline at end of file diff --git a/riscemu/decoder/__main__.py b/riscemu/decoder/__main__.py new file mode 100644 index 0000000..98fcd49 --- /dev/null +++ b/riscemu/decoder/__main__.py @@ -0,0 +1,17 @@ +if __name__ == '__main__': + import code + import readline + import rlcompleter + + from .decoder import * + from .formats import * + from instruction_table import * + from regs import * + + sess_vars = globals() + sess_vars.update(locals()) + + readline.set_completer(rlcompleter.Completer(sess_vars).complete) + readline.set_completer(rlcompleter.Completer(sess_vars).complete) + readline.parse_and_bind("tab: complete") + code.InteractiveConsole(sess_vars).interact(banner="Interaktive decoding session started...", exitmsg="Closing...") \ No newline at end of file diff --git a/riscemu/decoder/decoder.py b/riscemu/decoder/decoder.py new file mode 100644 index 0000000..d34dded --- /dev/null +++ b/riscemu/decoder/decoder.py @@ -0,0 +1,86 @@ +from .instruction_table import * +from typing import Tuple, List + + +def print_ins(ins: int): + print(" f7 rs2 rs1 f3 rd op") + print( + f"0b{ins >> 25 :07b}_{(ins >> 20) & 0b11111:05b}_{(ins >> 15) & 0b11111:03b}_{(ins >> 12) & 0b111:03b}_{(ins >> 7) & 0b11111:05b}_{ins & 0b1111111:07b}"); + + +STATIC_INSN = { + 0x00000013: ("nop", []), + 0x00008067: ("ret", []), + 0xfe010113: ("addi", ["sp", "sp", -32]), + 0x02010113: ("addi", ["sp", "sp", 32]), + 0x00100073: ("ebreak", []), + 0x00000073: ("ecall", []) +} + + +def int_from_ins(insn: bytearray): + return (insn[3] << (8 * 3)) + \ + (insn[2] << (8 * 2)) + \ + (insn[1] << 8) + \ + insn[0] + + +def name_from_insn(ins: int): + opcode = op(ins) + if opcode not in RV32: + print_ins(ins) + raise RuntimeError(f"Invalid opcode: {opcode:0x} in insn {ins:x}") + dec = RV32[opcode] + + if isinstance(dec, str): + return dec + + fun = funct3(ins) + if fun not in dec: + print_ins(ins) + raise RuntimeError(f"Invalid funct3: {fun:0x} in insn {ins:x}") + + dec = dec[fun] + if isinstance(dec, str): + return dec + + if opcode == 0x1c and fun == 0: + # we have ecall/ebreak + token = imm110(ins) + if token in dec: + return dec[token] + print_ins(ins) + raise RuntimeError(f"Invalid instruction in ebreak/ecall region: {ins:x}") + + fun = funct7(ins) + if fun in dec: + if opcode == 0x0C or (opcode == 0x04 and fun == 5): + mode = imm110(ins) + dec = dec[fun] + if mode in dec: + return dec[mode] + print_ins(ins) + raise RuntimeError("Unknown instruction!") + + return dec[fun] + + print_ins(ins) + raise RuntimeError(f"Invalid instruction: {ins:x}") + + +def decode(ins: bytearray) -> Tuple[str, List[Union[str, int]]]: + insn = int_from_ins(ins) + + if insn & 3 != 3: + print_ins(insn) + raise RuntimeError("Not a RV32 instruction!") + + if insn in STATIC_INSN: + return STATIC_INSN[insn] + + opcode = op(insn) + if opcode not in INSTRUCTION_ARGS_DECODER: + print_ins(insn) + raise RuntimeError("No instruction decoder found for instruction") + + return name_from_insn(insn), INSTRUCTION_ARGS_DECODER[opcode](insn) diff --git a/riscemu/decoder/formats.py b/riscemu/decoder/formats.py new file mode 100644 index 0000000..aaafb06 --- /dev/null +++ b/riscemu/decoder/formats.py @@ -0,0 +1,114 @@ +from typing import Dict, Callable, List, Union +from .regs import RISCV_REGS + +def op(ins: int): + return (ins >> 2) & 31 + + +def rd(ins: int): + return (ins >> 7) & 31 + + +def funct3(ins: int): + return (ins >> 12) & 7 + + +def rs1(ins: int): + return (ins >> 15) & 31 + + +def rs2(ins: int): + return (ins >> 20) & 31 + + +def funct7(ins: int): + return ins >> 25 + + +def imm110(ins: int): + return ins >> 20 + + +def imm3112(ins: int): + return ins >> 12 + + +def imm_i(ins: int): + return sign_extend(imm110(ins), 12) + + +def imm_s(ins: int): + num = (funct7(ins) << 5) + rd(ins) + return sign_extend(num, 12) + + +def imm_b(ins: int): + lower = rd(ins) + higher = funct7(ins) + + num = (lower & 0b11110) + ((higher & 0b0111111) << 5) + ((lower & 1) << 11) + ((higher >> 6) << 12) + return sign_extend(num, 13) + + +def imm_u(ins: int): + return sign_extend(imm3112(ins), 20) + + +def imm_j(ins: int): + imm = ins >> 12 + return sign_extend( + ((imm >> 8) & 0b1111111111) + + ((imm & 1) << 10) + + ((imm & 0b11111111) << 11) + + (imm & 0b10000000000000000000), 20 + ) + + +def sign_extend(num, bits): + sign_mask = 1 << (bits - 1) + return (num & (sign_mask - 1)) - (num & sign_mask) + + +def decode_i(ins: int) -> List[Union[str, int]]: + return [RISCV_REGS[rd(ins)], RISCV_REGS[rs1(ins)], imm_i(ins)] + + +def decode_b(ins: int) -> List[Union[str, int]]: + return [RISCV_REGS[rs1(ins)], RISCV_REGS[rs2(ins)], imm_b(ins)] + + +def decode_u(ins: int) -> List[Union[str, int]]: + return [RISCV_REGS[rd(ins)], imm_u(ins)] + + +def decode_r(ins: int) -> List[Union[str, int]]: + return [RISCV_REGS[rd(ins)], RISCV_REGS[rs1(ins)], RISCV_REGS[rs2(ins)]] + + +def decode_s(ins: int) -> List[Union[str, int]]: + return [RISCV_REGS[rs1(ins)], RISCV_REGS[rs2(ins)], imm_s(ins)] + + +def decode_j(ins: int) -> List[Union[str, int]]: + return [RISCV_REGS[rd(ins)], imm_j(ins)] + + +def decode_i_shamt(ins: int) -> List[Union[str, int]]: + if funct3(ins) in (1, 5): + return [RISCV_REGS[rd(ins)], RISCV_REGS[rs1(ins)], rs2(ins)] + else: + return [RISCV_REGS[rd(ins)], RISCV_REGS[rs1(ins)], imm110(ins)] + + +INSTRUCTION_ARGS_DECODER: Dict[int, Callable[[int], List[Union[str, int]]]] = { + 0x00: decode_i, + 0x04: decode_i_shamt, + 0x05: decode_u, + 0x08: decode_s, + 0x0C: decode_r, + 0x0D: decode_u, + 0x18: decode_b, + 0x19: decode_i, + 0x1b: decode_j, + 0x1c: decode_i +} diff --git a/riscemu/decoder/instruction_table.py b/riscemu/decoder/instruction_table.py new file mode 100644 index 0000000..4ab4417 --- /dev/null +++ b/riscemu/decoder/instruction_table.py @@ -0,0 +1,69 @@ +from collections import defaultdict +from .formats import * + +tbl = lambda: defaultdict(tbl) + +RV32 = tbl() +RV32[0x1b] = "jal" +RV32[0x0D] = "lui" +RV32[0x05] = "auipc" +RV32[0x19][0] = "jalr" + +RV32[0x04][0] = "addi" +RV32[0x04][1] = "slli" +RV32[0x04][2] = "slti" +RV32[0x04][3] = "sltiu" +RV32[0x04][4] = "xori" +RV32[0x04][5][0x00] = "srli" +RV32[0x04][5][0x20] = "srai" +RV32[0x04][6] = "ori" +RV32[0x04][7] = "andi" + +RV32[0x18][0] = "beq" +RV32[0x18][1] = "bne" +RV32[0x18][4] = "blt" +RV32[0x18][5] = "bge" +RV32[0x18][6] = "bltu" +RV32[0x18][7] = "bgeu" + +RV32[0x00][0] = "lb" +RV32[0x00][1] = "lh" +RV32[0x00][2] = "lw" +RV32[0x00][4] = "lbu" +RV32[0x00][5] = "lhu" + +RV32[0x08][0] = "sb" +RV32[0x08][1] = "sh" +RV32[0x08][2] = "sw" + +RV32[0x1c][1] = "csrrw" +RV32[0x1c][2] = "csrrs" +RV32[0x1c][3] = "csrrc" +RV32[0x1c][5] = "csrrwi" +RV32[0x1c][6] = "csrrsi" +RV32[0x1c][7] = "csrrci" + +RV32[0x1c][0][0] = "ecall" +RV32[0x1c][0][1] = "ebreak" + +RV32[0x0C][0][0] = "add" +RV32[0x0C][0][32] = "sub" +RV32[0x0C][1][0] = "sll" +RV32[0x0C][2][0] = "slt" +RV32[0x0C][3][0] = "sltu" +RV32[0x0C][4][0] = "xor" +RV32[0x0C][5][0] = "srl" +RV32[0x0C][5][32] = "sra" +RV32[0x0C][6][0] = "or" +RV32[0x0C][7][0] = "and" + +# rv32m +RV32[0x0C][0][1] = "mul" +RV32[0x0C][1][1] = "mulh" +RV32[0x0C][2][1] = "mulhsu" +RV32[0x0C][3][1] = "mulhu" +RV32[0x0C][4][1] = "div" +RV32[0x0C][5][1] = "divu" +RV32[0x0C][6][1] = "rem" +RV32[0x0C][7][1] = "remu" + diff --git a/riscemu/decoder/regs.py b/riscemu/decoder/regs.py new file mode 100644 index 0000000..44d70ac --- /dev/null +++ b/riscemu/decoder/regs.py @@ -0,0 +1,6 @@ +RISCV_REGS = [ + 'zero', 'ra', 'sp', 'gp', 'tp', 't0', 't1', 't2', + 's0', 's1', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', + 's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11', + 't3', 't4', 't5', 't6' +] diff --git a/riscemu/priv/PrivMMU.py b/riscemu/priv/PrivMMU.py new file mode 100644 index 0000000..e69de29 diff --git a/riscemu/priv/privmodes.py b/riscemu/priv/privmodes.py new file mode 100644 index 0000000..e69de29 -- 2.40.1 From 15da68995c17fd42bae741fb848d4c82423ad3b4 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sat, 22 May 2021 21:02:36 +0200 Subject: [PATCH 04/69] [priv] module now able to load and execute elf binaries --- requirements.txt | 1 + riscemu/priv/ElfLoader.py | 119 +++++++++++++++++++++++++++++++++++-- riscemu/priv/Exceptions.py | 12 ++++ riscemu/priv/PrivCPU.py | 38 ++++++++---- riscemu/priv/PrivMMU.py | 25 ++++++++ riscemu/priv/PrivRV32I.py | 81 ++++++++++++++++++++----- riscemu/priv/__main__.py | 19 ++---- riscemu/priv/privmodes.py | 7 +++ 8 files changed, 255 insertions(+), 47 deletions(-) diff --git a/requirements.txt b/requirements.txt index e69de29..7a447fe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1 @@ +pyelftools~=0.27 \ No newline at end of file diff --git a/riscemu/priv/ElfLoader.py b/riscemu/priv/ElfLoader.py index d61446b..549114e 100644 --- a/riscemu/priv/ElfLoader.py +++ b/riscemu/priv/ElfLoader.py @@ -1,9 +1,118 @@ -from ..Executable import Executable, MemorySection, MemoryFlags +from ..Executable import Executable, MemorySection, MemoryFlags, LoadedExecutable, LoadedMemorySection +from ..Exceptions import RiscemuBaseException +from ..helpers import FMT_PARSE, FMT_NONE +from ..colors import FMT_GREEN, FMT_BOLD + +FMT_ELF = FMT_GREEN + FMT_BOLD + +from .Exceptions import * + +from dataclasses import dataclass + +from typing import List, Dict, Union + +from elftools.elf.elffile import ELFFile +from elftools.elf.sections import Section, SymbolTableSection + +from ..decoder import decode # This requires pyelftools package! -class ElfExecutable(Executable): - def __init__(self, name): - from elftools.elf.elffile import ELFFile +INCLUDE_SEC = ('.text', '.stack', '.bss') + + +class ElfExecutable: + sections: List['ElfLoadedMemorySection'] + sections_by_name: Dict[str, 'ElfLoadedMemorySection'] + symbols: Dict[str, int] + run_ptr: int + + def __init__(self, name: str): + self.sections = list() + self.sections_by_name = dict() + self.symbols = dict() + + with open(name, 'rb') as f: + print(FMT_ELF + "[ElfLoader] Loading elf executable from: {}".format(name) + FMT_NONE) + self._read_elf(ELFFile(f)) + + def _read_elf(self, elf: ELFFile): + if not elf.header.e_machine == 'EM_RISCV': + raise InvalidElfException("Not a RISC-V elf file!") + if not elf.header.e_ident.EI_CLASS == 'ELFCLASS32': + raise InvalidElfException("Only 32bit executables are supported!") + + self.run_ptr = elf.header.e_entry; + + for sec in elf.iter_sections(): + if isinstance(sec, SymbolTableSection): + self._parse_symtab(sec) + continue + + if sec.name not in INCLUDE_SEC: + continue + + sec_ = self._lms_from_elf_sec(sec, 'kernel') + self.sections.append(sec_) + self.sections_by_name[sec.name] = sec_ + + def _lms_from_elf_sec(self, sec: Section, owner: str): + is_code = sec.name in ('.text',) + data = sec.data() + flags = MemoryFlags(is_code, is_code) + print(FMT_ELF + "[ElfLoader] Section {} at: {:X}".format(sec.name, sec.header.sh_addr) + FMT_NONE) + return ElfLoadedMemorySection( + sec.name, + sec.header.sh_addr, + sec.data_size, + data, + flags, + owner + ) + + def _parse_symtab(self, symtab: SymbolTableSection): + self.symbols = { + sym.name: sym.entry.st_value for sym in symtab.iter_symbols() if sym.name + } + + def load_user_elf(self, name: str): + pass + + +class InvalidElfException(RiscemuBaseException): + def __init__(self, msg: str): + super().__init__() + self.msg = msg + + def message(self): + return FMT_PARSE + "{}(\"{}\")".format(self.__class__.__name__, self.msg) + FMT_NONE + + +@dataclass(frozen=True) +class ElfInstruction: + name: str + args: List[Union[int, str]] + + def get_imm(self, num: int): + return self.args[-1] + + def get_imm_reg(self, num: int): + return self.args[-1], self.args[-2] + + def get_reg(self, num: int): + return self.args[num] + + def __repr__(self): + return "{:<8} {}".format( + self.name, + ", ".join(map(str, self.args)) + ) - with open(f) +class ElfLoadedMemorySection(LoadedMemorySection): + def read_instruction(self, offset): + if not self.flags.executable: + print(FMT_PARSE + "Reading instruction from non-executable memory!" + FMT_NONE) + raise InstructionAccessFault(offset + self.base) + if offset % 4 != 0: + raise InstructionAddressMisalignedTrap(offset + self.base) + return ElfInstruction(*decode(self.content[offset:offset + 4])) diff --git a/riscemu/priv/Exceptions.py b/riscemu/priv/Exceptions.py index 6a2eafa..37388ad 100644 --- a/riscemu/priv/Exceptions.py +++ b/riscemu/priv/Exceptions.py @@ -21,3 +21,15 @@ class CpuTrap(BaseException): class IllegalInstructionTrap(CpuTrap): def __init__(self): super().__init__(0, 2, 0) + + +class InstructionAddressMisalignedTrap(CpuTrap): + def __init__(self, addr: int): + super().__init__(0, 0, addr) + + +class InstructionAccessFault(CpuTrap): + def __init__(self, addr: int): + super().__init__(0, 1, addr) + + diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index b0e2a63..1a5fe32 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -11,6 +11,9 @@ from .Exceptions import * from .CSR import CSR from .PrivRV32I import PrivRV32I from ..instructions.RV32M import RV32M +from .PrivMMU import PrivMMU +from .ElfLoader import ElfExecutable +from .privmodes import PrivModes from collections import defaultdict @@ -20,13 +23,6 @@ if typing.TYPE_CHECKING: from riscemu import Executable, LoadedExecutable, LoadedInstruction from riscemu.instructions.InstructionSet import InstructionSet - -class PrivModes(IntEnum): - USER = 0 - SUPER = 1 - MACHINE = 3 - - class PrivCPU(CPU): """ This is a CPU that has different modes, instruction sets and registers. @@ -38,7 +34,7 @@ class PrivCPU(CPU): csr: CSR - INS_XLEN = 1 + INS_XLEN = 4 """ Size of an instruction in memory. Should be 4, but since our loading code is shit, instruction take up the equivalent of "1 byte" (this is actually impossible) @@ -48,12 +44,18 @@ class PrivCPU(CPU): super().__init__(conf, [PrivRV32I, RV32M]) self.mode: PrivModes = PrivModes.MACHINE + exec = ElfExecutable('kernel') + self.mmu = PrivMMU(exec) + self.pc = exec.run_ptr + self.syscall_int = None + # set up CSR self.csr = CSR() # TODO: Actually populate the CSR with real data (vendorID, heartID, machine implementation etc) self.csr.set('mhartid', 0) # core id self.csr.set('mimpid', 1) # implementation id - self.csr.set('misa', ()) # available ISA + # TODO: set correct misa + self.csr.set('misa', 1) # available ISA def _run(self, verbose=False): if self.pc <= 0: @@ -66,7 +68,7 @@ class PrivCPU(CPU): ins = self.mmu.read_ins(self.pc) if verbose: print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins)) - self.pc += 1 + self.pc += self.INS_XLEN self.run_instruction(ins) except CpuTrap as trap: mie = self.csr.get_mstatus('mie') @@ -97,8 +99,6 @@ class PrivCPU(CPU): else: # standard mode self.pc = (mtvec >> 2) - - 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) @@ -119,3 +119,17 @@ class PrivCPU(CPU): else: print() print(FMT_CPU + "Program stopped without exiting - perhaps you stopped the debugger?" + FMT_NONE) + + def load(self, e: riscemu.Executable): + raise NotImplementedError("Not supported!") + + def run_loaded(self, le: 'riscemu.LoadedExecutable'): + raise NotImplementedError("Not supported!") + + def get_tokenizer(self, tokenizer_input): + raise NotImplementedError("Not supported!") + + def run(self): + print(FMT_CPU + '[CPU] Started running from 0x{:08X} ({})'.format(self.pc, "kernel") + FMT_NONE) + self._run(True) + diff --git a/riscemu/priv/PrivMMU.py b/riscemu/priv/PrivMMU.py index e69de29..2d67af8 100644 --- a/riscemu/priv/PrivMMU.py +++ b/riscemu/priv/PrivMMU.py @@ -0,0 +1,25 @@ +from ..MMU import * + +import typing + +from .ElfLoader import ElfExecutable + +class PrivMMU(MMU): + def __init__(self, elf: ElfExecutable): + super(PrivMMU, self).__init__(conf=RunConfig()) + + self.binaries.append(elf) + for sec in elf.sections: + self.sections.append(sec) + + def load_bin(self, exe: Executable) -> LoadedExecutable: + raise NotImplementedError("This is a privMMU, it's initialized with a single ElfExecutable!") + + def allocate_section(self, name: str, req_size: int, flag: MemoryFlags): + raise NotImplementedError("Not supported!") + + + + + + diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index dc56df9..af31fd3 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -4,24 +4,28 @@ RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ -from riscemu.instructions.RV32I import * +from ..instructions.RV32I import * from ..Exceptions import INS_NOT_IMPLEMENTED -from riscemu.priv.PrivCPU import PrivModes, PrivCPU from .Exceptions import * +from .privmodes import PrivModes +import typing + +if typing.TYPE_CHECKING: + from riscemu.priv.PrivCPU import PrivCPU class PrivRV32I(RV32I): - cpu: PrivCPU + cpu: 'PrivCPU' """ This is an extension of RV32I, written for the PrivCPU class """ def instruction_csrrw(self, ins: 'LoadedInstruction'): - rd, off, rs = self.parse_crs_ins(ins) + rd, rs, ind = self.parse_crs_ins(ins) if rd != 'zero': - old_val = int_from_bytes(self.cpu.csr.read(off, 4)) + old_val = int_from_bytes(self.cpu.csr[ind]) self.regs.set(rd, old_val) - self.cpu.csr.write(off, 4, int_to_bytes(rs)) + self.cpu.csr.set(ind, rs) def instruction_csrrs(self, ins: 'LoadedInstruction'): INS_NOT_IMPLEMENTED(ins) @@ -63,17 +67,64 @@ class PrivRV32I(RV32I): Overwrite the scall from userspace RV32I """ if self.cpu.mode == PrivModes.USER: - raise CpuTrap(0, 8) # ecall from U mode + raise CpuTrap(0, 8) # ecall from U mode elif self.cpu.mode == PrivModes.SUPER: - raise CpuTrap(0, 9) # ecall from S mode - should not happen + raise CpuTrap(0, 9) # ecall from S mode - should not happen elif self.cpu.mode == PrivModes.MACHINE: - raise CpuTrap(0, 11) # ecall from M mode - - - - - + raise CpuTrap(0, 11) # ecall from M mode + + def instruction_beq(self, ins: 'LoadedInstruction'): + rs1, rs2, dst = self.parse_rs_rs_imm(ins) + if rs1 == rs2: + self.pc += dst + + def instruction_bne(self, ins: 'LoadedInstruction'): + rs1, rs2, dst = self.parse_rs_rs_imm(ins) + if rs1 != rs2: + self.pc += dst + + def instruction_blt(self, ins: 'LoadedInstruction'): + rs1, rs2, dst = self.parse_rs_rs_imm(ins) + if rs1 < rs2: + self.pc += dst + + def instruction_bge(self, ins: 'LoadedInstruction'): + rs1, rs2, dst = self.parse_rs_rs_imm(ins) + if rs1 >= rs2: + self.pc += dst + + def instruction_bltu(self, ins: 'LoadedInstruction'): + rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) + if rs1 < rs2: + self.pc += dst + + def instruction_bgeu(self, ins: 'LoadedInstruction'): + rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) + if rs1 >= rs2: + self.pc += dst + + # technically deprecated + def instruction_j(self, ins: 'LoadedInstruction'): + raise NotImplementedError("Should never be reached!") + + def instruction_jal(self, ins: 'LoadedInstruction'): + ASSERT_LEN(ins.args, 2) + reg = ins.get_reg(0) + addr = ins.get_imm(1) + self.regs.set(reg, self.pc) + self.pc += addr + + def instruction_jalr(self, ins: 'LoadedInstruction'): + ASSERT_LEN(ins.args, 3) + rd, rs, imm = self.parse_rd_rs_imm(ins) + self.regs.set(rd, self.pc) + self.pc = rs + imm def parse_crs_ins(self, ins: 'LoadedInstruction'): ASSERT_LEN(ins.args, 3) - return ins.get_reg(0), ins.get_imm(1), self.get_reg_content(ins, 2) + return ins.get_reg(0), self.get_reg_content(ins, 1), ins.get_imm(2) + + def parse_mem_ins(self, ins: 'LoadedInstruction') -> Tuple[str, int]: + ASSERT_LEN(ins.args, 3) + print("dop") + return ins.get_reg(1), self.get_reg_content(ins, 0) + ins.get_imm(2) diff --git a/riscemu/priv/__main__.py b/riscemu/priv/__main__.py index dd38576..0cd2184 100644 --- a/riscemu/priv/__main__.py +++ b/riscemu/priv/__main__.py @@ -1,4 +1,4 @@ -from .PrivCPU import * +from .PrivCPU import PrivCPU, RunConfig from ..Tokenizer import RiscVInput from ..ExecutableParser import ExecutableParser @@ -11,17 +11,6 @@ if __name__ == '__main__': cpu = PrivCPU(RunConfig()) - try: - loaded_exe = None - for file in files: - tk = cpu.get_tokenizer(RiscVInput.from_file(file)) - tk.tokenize() - loaded_exe = cpu.load(ExecutableParser(tk).parse()) - # run the last loaded executable - cpu.run_loaded(loaded_exe) - except RiscemuBaseException as e: - print("Error while parsing: {}".format(e.message())) - import traceback - - traceback.print_exception(type(e), e, e.__traceback__) - sys.exit(1) + cpu.run() + + diff --git a/riscemu/priv/privmodes.py b/riscemu/priv/privmodes.py index e69de29..1e58bc2 100644 --- a/riscemu/priv/privmodes.py +++ b/riscemu/priv/privmodes.py @@ -0,0 +1,7 @@ +from enum import IntEnum + + +class PrivModes(IntEnum): + USER = 0 + SUPER = 1 + MACHINE = 3 -- 2.40.1 From c48a5efee3973b7b5059b0e5a39a8d31a4212fae Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sat, 22 May 2021 21:03:37 +0200 Subject: [PATCH 05/69] [cpu] fixed formatting to include cpu class extensions --- riscemu/CPU.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/riscemu/CPU.py b/riscemu/CPU.py index f116a4e..620577d 100644 --- a/riscemu/CPU.py +++ b/riscemu/CPU.py @@ -178,7 +178,8 @@ class CPU: """ Returns a representation of the CPU and some of its state. """ - return "CPU(pc=0x{:08X}, cycle={}, exit={}, instructions={})".format( + return "{}(pc=0x{:08X}, cycle={}, exit={}, instructions={})".format( + self.__class__.__name__, self.pc, self.cycle, self.exit, -- 2.40.1 From 1bdf2e6efeab3773c91b681794ecacb5928b55f2 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sat, 22 May 2021 21:03:56 +0200 Subject: [PATCH 06/69] [mmu] fixed typo in docstring --- riscemu/MMU.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/riscemu/MMU.py b/riscemu/MMU.py index e4e4bfc..5777f67 100644 --- a/riscemu/MMU.py +++ b/riscemu/MMU.py @@ -49,7 +49,7 @@ class MMU: def __init__(self, conf: RunConfig): """ - Create a new MMU, respeccting the active RunConfiguration + Create a new MMU, respecting the active RunConfiguration :param conf: The config to respect """ -- 2.40.1 From ee0aac30c43b4c1030d7b21db344d2bd4c192270 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sat, 22 May 2021 21:04:43 +0200 Subject: [PATCH 07/69] [instructions] moved regs and mmu to properties to work with janky PrivCPU --- riscemu/instructions/InstructionSet.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/riscemu/instructions/InstructionSet.py b/riscemu/instructions/InstructionSet.py index f5d0f01..6b55e7d 100644 --- a/riscemu/instructions/InstructionSet.py +++ b/riscemu/instructions/InstructionSet.py @@ -29,8 +29,6 @@ class InstructionSet(ABC): """ self.name = self.__class__.__name__ self.cpu = cpu - self.mmu = cpu.mmu - self.regs = cpu.regs def load(self) -> Dict[str, Callable[['LoadedInstruction'], None]]: """ @@ -132,6 +130,14 @@ class InstructionSet(ABC): def pc(self, val): self.cpu.pc = val + @property + def mmu(self): + return self.cpu.mmu + + @property + def regs(self): + return self.cpu.regs + def __repr__(self): return "InstructionSet[{}] with {} instructions".format( self.__class__.__name__, -- 2.40.1 From c9a136d595c53d217148f0764c860e545b1ae303 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sat, 22 May 2021 21:05:14 +0200 Subject: [PATCH 08/69] [instructions] fixed error in auipc command --- riscemu/instructions/RV32I.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/riscemu/instructions/RV32I.py b/riscemu/instructions/RV32I.py index 20a64aa..d8681d0 100644 --- a/riscemu/instructions/RV32I.py +++ b/riscemu/instructions/RV32I.py @@ -154,8 +154,7 @@ class RV32I(InstructionSet): ASSERT_LEN(ins.args, 2) reg = ins.get_reg(0) imm = to_unsigned(ins.get_imm(1)) - self.pc += (imm << 12) - self.regs.set(reg, self.pc) + self.regs.set(reg, self.pc + (imm << 12)) def instruction_xor(self, ins: 'LoadedInstruction'): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) -- 2.40.1 From 0475d8d3849409bbcdd9d7f2e225bf0131712613 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sun, 23 May 2021 10:42:04 +0200 Subject: [PATCH 09/69] [CPU] added instruction XLEN attribute to CPU class to support multiple instruction lengths --- riscemu/CPU.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/riscemu/CPU.py b/riscemu/CPU.py index 620577d..efb1892 100644 --- a/riscemu/CPU.py +++ b/riscemu/CPU.py @@ -34,6 +34,9 @@ class CPU: It is initialized with a configuration and a list of instruction sets. """ + + INS_XLEN = 1 + def __init__(self, conf: RunConfig, instruction_sets: List[Type['riscemu.InstructionSet']]): """ Creates a CPU instance. @@ -115,12 +118,12 @@ class CPU: self.cycle += 1 ins = self.mmu.read_ins(self.pc) print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins)) - self.pc += 1 + 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 -= 1 + self.pc -= self.INS_XLEN print(ex.message()) def _run(self, verbose=False): @@ -133,13 +136,13 @@ class CPU: ins = self.mmu.read_ins(self.pc) if verbose: print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins)) - self.pc += 1 + 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) print(ex.message()) - self.pc -= 1 + self.pc -= self.INS_XLEN if self.active_debug: print(FMT_CPU + "[CPU] Returning to debugger!" + FMT_NONE) -- 2.40.1 From f3959be843e640ea5c108576a171026b54878614 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sun, 23 May 2021 10:44:27 +0200 Subject: [PATCH 10/69] [decoder] now returning instruction number as third return value --- riscemu/decoder/__main__.py | 3 +-- riscemu/decoder/decoder.py | 18 +++++++++--------- riscemu/priv/ElfLoader.py | 1 + 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/riscemu/decoder/__main__.py b/riscemu/decoder/__main__.py index 98fcd49..e109c97 100644 --- a/riscemu/decoder/__main__.py +++ b/riscemu/decoder/__main__.py @@ -5,8 +5,7 @@ if __name__ == '__main__': from .decoder import * from .formats import * - from instruction_table import * - from regs import * + from .instruction_table import * sess_vars = globals() sess_vars.update(locals()) diff --git a/riscemu/decoder/decoder.py b/riscemu/decoder/decoder.py index d34dded..b218ccd 100644 --- a/riscemu/decoder/decoder.py +++ b/riscemu/decoder/decoder.py @@ -8,13 +8,13 @@ def print_ins(ins: int): f"0b{ins >> 25 :07b}_{(ins >> 20) & 0b11111:05b}_{(ins >> 15) & 0b11111:03b}_{(ins >> 12) & 0b111:03b}_{(ins >> 7) & 0b11111:05b}_{ins & 0b1111111:07b}"); -STATIC_INSN = { - 0x00000013: ("nop", []), - 0x00008067: ("ret", []), - 0xfe010113: ("addi", ["sp", "sp", -32]), - 0x02010113: ("addi", ["sp", "sp", 32]), - 0x00100073: ("ebreak", []), - 0x00000073: ("ecall", []) +STATIC_INSN: Dict[int, Tuple[str, List[Union[str, int]], int]] = { + 0x00000013: ("nop", [], 0x00000013), + 0x00008067: ("ret", [], 0x00008067), + 0xfe010113: ("addi", ["sp", "sp", -32], 0xfe010113), + 0x02010113: ("addi", ["sp", "sp", 32], 0x02010113), + 0x00100073: ("ebreak", [], 0x00100073), + 0x00000073: ("ecall", [], 0x00000073), } @@ -68,7 +68,7 @@ def name_from_insn(ins: int): raise RuntimeError(f"Invalid instruction: {ins:x}") -def decode(ins: bytearray) -> Tuple[str, List[Union[str, int]]]: +def decode(ins: bytearray) -> Tuple[str, List[Union[str, int]], int]: insn = int_from_ins(ins) if insn & 3 != 3: @@ -83,4 +83,4 @@ def decode(ins: bytearray) -> Tuple[str, List[Union[str, int]]]: print_ins(insn) raise RuntimeError("No instruction decoder found for instruction") - return name_from_insn(insn), INSTRUCTION_ARGS_DECODER[opcode](insn) + return name_from_insn(insn), INSTRUCTION_ARGS_DECODER[opcode](insn), insn diff --git a/riscemu/priv/ElfLoader.py b/riscemu/priv/ElfLoader.py index 549114e..90ca06e 100644 --- a/riscemu/priv/ElfLoader.py +++ b/riscemu/priv/ElfLoader.py @@ -92,6 +92,7 @@ class InvalidElfException(RiscemuBaseException): class ElfInstruction: name: str args: List[Union[int, str]] + encoded: int def get_imm(self, num: int): return self.args[-1] -- 2.40.1 From 3f11cd84cac1260002476504d1592e260449e7db Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sun, 23 May 2021 12:58:47 +0200 Subject: [PATCH 11/69] [decoder] fixed error with decoding slli type instructions --- riscemu/decoder/formats.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/riscemu/decoder/formats.py b/riscemu/decoder/formats.py index aaafb06..ccca92f 100644 --- a/riscemu/decoder/formats.py +++ b/riscemu/decoder/formats.py @@ -96,8 +96,7 @@ def decode_j(ins: int) -> List[Union[str, int]]: def decode_i_shamt(ins: int) -> List[Union[str, int]]: if funct3(ins) in (1, 5): return [RISCV_REGS[rd(ins)], RISCV_REGS[rs1(ins)], rs2(ins)] - else: - return [RISCV_REGS[rd(ins)], RISCV_REGS[rs1(ins)], imm110(ins)] + return decode_i(ins) INSTRUCTION_ARGS_DECODER: Dict[int, Callable[[int], List[Union[str, int]]]] = { -- 2.40.1 From 3a79bfdada1b298e49361428400567284603bb33 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sun, 23 May 2021 12:59:59 +0200 Subject: [PATCH 12/69] [ElfLoader] also loading .sdata and .sbss sections now --- riscemu/priv/ElfLoader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/riscemu/priv/ElfLoader.py b/riscemu/priv/ElfLoader.py index 90ca06e..2f3397b 100644 --- a/riscemu/priv/ElfLoader.py +++ b/riscemu/priv/ElfLoader.py @@ -18,7 +18,7 @@ from ..decoder import decode # This requires pyelftools package! -INCLUDE_SEC = ('.text', '.stack', '.bss') +INCLUDE_SEC = ('.text', '.stack', '.bss', '.sdata', '.sbss') class ElfExecutable: -- 2.40.1 From 55be71dcc3d3cec00157eacb4811567777526904 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Mon, 24 May 2021 10:05:34 +0200 Subject: [PATCH 13/69] [CSR] added time and timeh csr codes --- riscemu/priv/CSR.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index 35e7277..c14ba6f 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -52,6 +52,8 @@ class CSR: 'marchid': 0xF12, 'mimpid': 0xF13, 'mhartid': 0xF14, + 'time': 0xc01, + 'timeh': 0xc81, } """ Translation for named registers -- 2.40.1 From ed6912a0606356ce77d7c4ce5116f1ac8b159c34 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Mon, 24 May 2021 10:07:21 +0200 Subject: [PATCH 14/69] [ElfLoader] added bounds check to elf loader and casting binary data to bytearray --- riscemu/priv/ElfLoader.py | 41 ++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/riscemu/priv/ElfLoader.py b/riscemu/priv/ElfLoader.py index 2f3397b..64bb140 100644 --- a/riscemu/priv/ElfLoader.py +++ b/riscemu/priv/ElfLoader.py @@ -1,20 +1,17 @@ -from ..Executable import Executable, MemorySection, MemoryFlags, LoadedExecutable, LoadedMemorySection -from ..Exceptions import RiscemuBaseException -from ..helpers import FMT_PARSE, FMT_NONE -from ..colors import FMT_GREEN, FMT_BOLD - -FMT_ELF = FMT_GREEN + FMT_BOLD - -from .Exceptions import * - from dataclasses import dataclass - from typing import List, Dict, Union from elftools.elf.elffile import ELFFile from elftools.elf.sections import Section, SymbolTableSection +from .Exceptions import * +from ..Exceptions import RiscemuBaseException +from ..Executable import MemoryFlags, LoadedMemorySection from ..decoder import decode +from ..helpers import FMT_PARSE, FMT_NONE, FMT_GREEN, FMT_BOLD + +FMT_ELF = FMT_GREEN + FMT_BOLD + # This requires pyelftools package! @@ -52,13 +49,11 @@ class ElfExecutable: if sec.name not in INCLUDE_SEC: continue - sec_ = self._lms_from_elf_sec(sec, 'kernel') - self.sections.append(sec_) - self.sections_by_name[sec.name] = sec_ + self.add_sec(self._lms_from_elf_sec(sec, 'kernel')) def _lms_from_elf_sec(self, sec: Section, owner: str): is_code = sec.name in ('.text',) - data = sec.data() + data = bytearray(sec.data()) flags = MemoryFlags(is_code, is_code) print(FMT_ELF + "[ElfLoader] Section {} at: {:X}".format(sec.name, sec.header.sh_addr) + FMT_NONE) return ElfLoadedMemorySection( @@ -75,8 +70,18 @@ class ElfExecutable: sym.name: sym.entry.st_value for sym in symtab.iter_symbols() if sym.name } - def load_user_elf(self, name: str): - pass + def add_sec(self, new_sec: 'ElfLoadedMemorySection'): + for sec in self.sections: + if sec.base < sec.end <= new_sec.base or sec.end > sec.base >= new_sec.end: + continue + else: + print(FMT_ELF + "[ElfLoader] Invalid elf layout: Two sections overlap: \n\t{}\n\t{}".format( + sec, new_sec + ) + FMT_NONE) + raise RuntimeError("Cannot load elf with overlapping sections!") + + self.sections.append(new_sec) + self.sections_by_name[new_sec.name] = new_sec class InvalidElfException(RiscemuBaseException): @@ -117,3 +122,7 @@ class ElfLoadedMemorySection(LoadedMemorySection): if offset % 4 != 0: raise InstructionAddressMisalignedTrap(offset + self.base) return ElfInstruction(*decode(self.content[offset:offset + 4])) + + @property + def end(self): + return self.size + self.base -- 2.40.1 From 6bd5cd1598f9ad90823ce91033f371899546e653 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Mon, 24 May 2021 10:08:01 +0200 Subject: [PATCH 15/69] [ElfLoader] better formatting for load and save instructions --- riscemu/priv/ElfLoader.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/riscemu/priv/ElfLoader.py b/riscemu/priv/ElfLoader.py index 64bb140..09ac73d 100644 --- a/riscemu/priv/ElfLoader.py +++ b/riscemu/priv/ElfLoader.py @@ -109,11 +109,18 @@ class ElfInstruction: return self.args[num] def __repr__(self): + if self.name in ('sw', 'sh', 'sb', 'lb', 'lh', 'lb', 'lbu', 'lhu'): + args = "{}, {}({})".format( + self.args[1], self.args[2], self.args[0] + ) + else: + args = ", ".join(map(str, self.args)) return "{:<8} {}".format( self.name, - ", ".join(map(str, self.args)) + args ) + class ElfLoadedMemorySection(LoadedMemorySection): def read_instruction(self, offset): if not self.flags.executable: -- 2.40.1 From db2b0b314bb3ca6dbe3204768b86d1ddab6f5344 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Mon, 24 May 2021 10:08:53 +0200 Subject: [PATCH 16/69] [PrivCPU, PrivRV32I] fix for relative jumps and branches --- riscemu/priv/PrivCPU.py | 2 +- riscemu/priv/PrivRV32I.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 1a5fe32..f4463c0 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -68,8 +68,8 @@ class PrivCPU(CPU): 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.pc += self.INS_XLEN except CpuTrap as trap: mie = self.csr.get_mstatus('mie') if not mie: diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index af31fd3..3a25d52 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -76,12 +76,12 @@ class PrivRV32I(RV32I): def instruction_beq(self, ins: 'LoadedInstruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 == rs2: - self.pc += dst + self.pc += dst - 4 def instruction_bne(self, ins: 'LoadedInstruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 != rs2: - self.pc += dst + self.pc += dst - 4 def instruction_blt(self, ins: 'LoadedInstruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins) @@ -91,17 +91,17 @@ class PrivRV32I(RV32I): def instruction_bge(self, ins: 'LoadedInstruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 >= rs2: - self.pc += dst + self.pc += dst - 4 def instruction_bltu(self, ins: 'LoadedInstruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) if rs1 < rs2: - self.pc += dst + self.pc += dst - 4 def instruction_bgeu(self, ins: 'LoadedInstruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) if rs1 >= rs2: - self.pc += dst + self.pc += dst - 4 # technically deprecated def instruction_j(self, ins: 'LoadedInstruction'): @@ -112,13 +112,13 @@ class PrivRV32I(RV32I): reg = ins.get_reg(0) addr = ins.get_imm(1) self.regs.set(reg, self.pc) - self.pc += addr + self.pc += addr - 4 def instruction_jalr(self, ins: 'LoadedInstruction'): ASSERT_LEN(ins.args, 3) rd, rs, imm = self.parse_rd_rs_imm(ins) self.regs.set(rd, self.pc) - self.pc = rs + imm + self.pc = rs + imm - 4 def parse_crs_ins(self, ins: 'LoadedInstruction'): ASSERT_LEN(ins.args, 3) -- 2.40.1 From 504407c0d988e851a5d16760db9ac72296644678 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Mon, 24 May 2021 14:34:35 +0200 Subject: [PATCH 17/69] [CSR] adding callbacks to each csr block --- riscemu/priv/CSR.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index c14ba6f..e39e06c 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -1,4 +1,4 @@ -from typing import Dict, Union +from typing import Dict, Union, Callable from collections import defaultdict MSTATUS_OFFSETS = { @@ -59,14 +59,18 @@ class CSR: Translation for named registers """ + listeners: Dict[int, Callable[[int, int], None]] + def __init__(self): self.regs = defaultdict(lambda: 0) + self.listeners = defaultdict(lambda: (lambda x, y: ())) def set(self, addr: Union[str, int], val: int): if isinstance(addr, str): if not addr in self.name_to_addr: print("Unknown CSR register {}".format(addr)) addr = self.name_to_addr[addr] + self.listeners[addr](self.regs[addr], val) self.regs[addr] = val def get(self, addr: Union[str, int]): @@ -76,6 +80,13 @@ class CSR: addr = self.name_to_addr[addr] return self.regs[addr] + def set_listener(self, addr: Union[str, int], listener: Callable[[int, int], None]): + if isinstance(addr, str): + if not addr in self.name_to_addr: + print("Unknown CSR register {}".format(addr)) + addr = self.name_to_addr[addr] + self.listeners[addr] = listener + # mstatus properties def set_mstatus(self, name: str, val: int): """ -- 2.40.1 From c4cd83701f5139a79b9ae47ddfa4d34045e1b8f0 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Mon, 24 May 2021 15:51:05 +0200 Subject: [PATCH 18/69] [CSR, PrivCPU] Added csr callback registration through decorator --- riscemu/CPU.py | 4 ++-- riscemu/priv/CSR.py | 8 ++++++++ riscemu/priv/PrivCPU.py | 27 ++++++++++++++++++++------- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/riscemu/CPU.py b/riscemu/CPU.py index efb1892..469092f 100644 --- a/riscemu/CPU.py +++ b/riscemu/CPU.py @@ -47,8 +47,8 @@ class CPU: # setup CPU states self.pc = 0 self.cycle = 0 - self.exit = False - self.exit_code = 0 + self.exit: bool = False + self.exit_code: int = 0 self.conf = conf self.active_debug = False # if a debugging session is currently runnign diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index e39e06c..50ec515 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -1,5 +1,6 @@ from typing import Dict, Union, Callable from collections import defaultdict +from functools import wraps MSTATUS_OFFSETS = { 'uie': 0, @@ -54,6 +55,7 @@ class CSR: 'mhartid': 0xF14, 'time': 0xc01, 'timeh': 0xc81, + 'halt': 0x789 } """ Translation for named registers @@ -112,3 +114,9 @@ class CSR: off = MSTATUS_OFFSETS[name] mask = (2**size - 1) << off return (self.get('mstatus') & mask) >> off + + def callback(self, addr: Union[str, int]): + def inner(func: Callable[[int, int], None]): + self.set_listener(addr, func) + return func + return inner \ No newline at end of file diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index f4463c0..d46e7cc 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -49,13 +49,8 @@ class PrivCPU(CPU): self.pc = exec.run_ptr self.syscall_int = None - # set up CSR - self.csr = CSR() - # TODO: Actually populate the CSR with real data (vendorID, heartID, machine implementation etc) - self.csr.set('mhartid', 0) # core id - self.csr.set('mimpid', 1) # implementation id - # TODO: set correct misa - self.csr.set('misa', 1) # available ISA + # init csr + self._init_csr() def _run(self, verbose=False): if self.pc <= 0: @@ -133,3 +128,21 @@ class PrivCPU(CPU): print(FMT_CPU + '[CPU] Started running from 0x{:08X} ({})'.format(self.pc, "kernel") + FMT_NONE) self._run(True) + def _init_csr(self): + # set up CSR + self.csr = CSR() + # TODO: Actually populate the CSR with real data (vendorID, heartID, machine implementation etc) + self.csr.set('mhartid', 0) # core id + self.csr.set('mimpid', 1) # implementation id + # TODO: set correct misa + self.csr.set('misa', 1) # available ISA + + @self.csr.callback('halt') + def halt(old: int, new: int): + if new != 0: + self.exit = True + self.exit_code = new + + @self.csr.callback('mstatus') + def mstatus(old: int, new: int): + pass -- 2.40.1 From 291f44a19267df16d5ff0e8582a604d1a26a177d Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 25 May 2021 10:24:24 +0200 Subject: [PATCH 19/69] [CSR] unknown csr names now fail without exception --- riscemu/priv/CSR.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index 50ec515..26b204c 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -65,20 +65,22 @@ class CSR: def __init__(self): self.regs = defaultdict(lambda: 0) - self.listeners = defaultdict(lambda: (lambda x, y: ())) + self.listeners = defaultdict(lambda: (lambda x, y: None)) def set(self, addr: Union[str, int], val: int): if isinstance(addr, str): - if not addr in self.name_to_addr: + if addr not in self.name_to_addr: print("Unknown CSR register {}".format(addr)) + return addr = self.name_to_addr[addr] self.listeners[addr](self.regs[addr], val) self.regs[addr] = val def get(self, addr: Union[str, int]): if isinstance(addr, str): - if not addr in self.name_to_addr: + if addr not in self.name_to_addr: print("Unknown CSR register {}".format(addr)) + return addr = self.name_to_addr[addr] return self.regs[addr] -- 2.40.1 From 49b59cd46a2f25226af54918355092acae203e9d Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 25 May 2021 10:49:54 +0200 Subject: [PATCH 20/69] [CSR] added read/write checks and unified name to addr resuloution --- riscemu/priv/CSR.py | 46 ++++++++++++++++++++++++--------------- riscemu/priv/PrivRV32I.py | 12 ++++++---- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index 26b204c..ef35763 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -1,6 +1,7 @@ -from typing import Dict, Union, Callable +from typing import Dict, Union, Callable, Optional from collections import defaultdict -from functools import wraps +from .privmodes import PrivModes +from .Exceptions import IllegalInstructionTrap MSTATUS_OFFSETS = { 'uie': 0, @@ -68,27 +69,22 @@ class CSR: self.listeners = defaultdict(lambda: (lambda x, y: None)) def set(self, addr: Union[str, int], val: int): - if isinstance(addr, str): - if addr not in self.name_to_addr: - print("Unknown CSR register {}".format(addr)) - return - addr = self.name_to_addr[addr] + addr = self._addr_to_name(addr) + if addr is None: + return self.listeners[addr](self.regs[addr], val) self.regs[addr] = val def get(self, addr: Union[str, int]): - if isinstance(addr, str): - if addr not in self.name_to_addr: - print("Unknown CSR register {}".format(addr)) - return - addr = self.name_to_addr[addr] + addr = self._addr_to_name(addr) + if addr is None: + return return self.regs[addr] def set_listener(self, addr: Union[str, int], listener: Callable[[int, int], None]): - if isinstance(addr, str): - if not addr in self.name_to_addr: - print("Unknown CSR register {}".format(addr)) - addr = self.name_to_addr[addr] + addr = self._addr_to_name(addr) + if addr is None: + return self.listeners[addr] = listener # mstatus properties @@ -121,4 +117,20 @@ class CSR: def inner(func: Callable[[int, int], None]): self.set_listener(addr, func) return func - return inner \ No newline at end of file + return inner + + def assert_can_read(self, mode: PrivModes, addr: int): + if (addr >> 8) & 3 > mode.value(): + raise IllegalInstructionTrap() + + def assert_can_write(self, mode: PrivModes, addr: int): + if (addr >> 8) & 3 > mode.value() or addr >> 10 == 11: + raise IllegalInstructionTrap() + + def _addr_to_name(self, addr: Union[str, int]) -> Optional[int]: + if isinstance(addr, str): + if addr not in self.name_to_addr: + print("Unknown CSR register {}".format(addr)) + return None + return self.name_to_addr[addr] + return addr diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index 3a25d52..f0b5c23 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -21,11 +21,15 @@ class PrivRV32I(RV32I): """ def instruction_csrrw(self, ins: 'LoadedInstruction'): - rd, rs, ind = self.parse_crs_ins(ins) + rd, rs, csr_addr = self.parse_crs_ins(ins) if rd != 'zero': - old_val = int_from_bytes(self.cpu.csr[ind]) + self.cpu.csr.assert_can_read(self.cpu.mode, csr_addr) + old_val = self.cpu.csr.get(csr_addr) self.regs.set(rd, old_val) - self.cpu.csr.set(ind, rs) + if rs != 'zero': + new_val = self.regs.get(rs) + self.cpu.csr.assert_can_write(self.cpu.mode, csr_addr) + self.cpu.csr.set(csr_addr, new_val) def instruction_csrrs(self, ins: 'LoadedInstruction'): INS_NOT_IMPLEMENTED(ins) @@ -122,7 +126,7 @@ class PrivRV32I(RV32I): def parse_crs_ins(self, ins: 'LoadedInstruction'): ASSERT_LEN(ins.args, 3) - return ins.get_reg(0), self.get_reg_content(ins, 1), ins.get_imm(2) + return ins.get_reg(0), ins.get_reg(1), ins.get_imm(2) def parse_mem_ins(self, ins: 'LoadedInstruction') -> Tuple[str, int]: ASSERT_LEN(ins.args, 3) -- 2.40.1 From a1f29b9d97d2be6adfd5a11f7317d4b1af4688c9 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 25 May 2021 11:14:18 +0200 Subject: [PATCH 21/69] [CPU] cleaned up constructor --- riscemu/priv/PrivCPU.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index d46e7cc..6916cc1 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -44,9 +44,9 @@ class PrivCPU(CPU): super().__init__(conf, [PrivRV32I, RV32M]) self.mode: PrivModes = PrivModes.MACHINE - exec = ElfExecutable('kernel') - self.mmu = PrivMMU(exec) - self.pc = exec.run_ptr + kernel = ElfExecutable('kernel') + self.mmu = PrivMMU(kernel) + self.pc = kernel.run_ptr self.syscall_int = None # init csr -- 2.40.1 From 6653ef7e7c1698f77f38fe9a7c6a5d95762fcd82 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 25 May 2021 11:14:52 +0200 Subject: [PATCH 22/69] [CPU] set correct MISA --- riscemu/priv/PrivCPU.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 6916cc1..43fd4df 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -131,11 +131,11 @@ class PrivCPU(CPU): def _init_csr(self): # set up CSR self.csr = CSR() - # TODO: Actually populate the CSR with real data (vendorID, heartID, machine implementation etc) self.csr.set('mhartid', 0) # core id + # TODO: set correct value self.csr.set('mimpid', 1) # implementation id - # TODO: set correct misa - self.csr.set('misa', 1) # available ISA + # set mxl to 1 (32 bit) and set bits for i and m isa + self.csr.set('misa', (1 << 30) + (1 << 8) + (1 << 12)) # available ISA @self.csr.callback('halt') def halt(old: int, new: int): -- 2.40.1 From 7239212729aac13b1fb59cff54d67d9fd8af005c Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 25 May 2021 23:49:37 +0200 Subject: [PATCH 23/69] [CSR] adding virtual csr registers --- riscemu/priv/CSR.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index ef35763..32bbb97 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -41,6 +41,11 @@ class CSR: All Control and Status Registers are stored here """ + virtual_regs: Dict[int, Callable[[], int]] + """ + list of virtual CSR registers, with values computed on read + """ + name_to_addr: Dict[str, int] = { 'mstatus': 0x300, 'misa': 0x301, @@ -56,7 +61,9 @@ class CSR: 'mhartid': 0xF14, 'time': 0xc01, 'timeh': 0xc81, - 'halt': 0x789 + 'halt': 0x789, + 'mtimecmp': 0x780, + 'mtimecmph': 0x781, } """ Translation for named registers @@ -67,6 +74,7 @@ class CSR: def __init__(self): self.regs = defaultdict(lambda: 0) self.listeners = defaultdict(lambda: (lambda x, y: None)) + #TODO: implement write masks (bitmasks which control writeable bits in registers def set(self, addr: Union[str, int], val: int): addr = self._addr_to_name(addr) @@ -75,15 +83,18 @@ class CSR: self.listeners[addr](self.regs[addr], val) self.regs[addr] = val - def get(self, addr: Union[str, int]): + def get(self, addr: Union[str, int]) -> int: addr = self._addr_to_name(addr) if addr is None: return + if addr in self.virtual_regs: + return self.virtual_regs[addr]() return self.regs[addr] def set_listener(self, addr: Union[str, int], listener: Callable[[int, int], None]): addr = self._addr_to_name(addr) if addr is None: + print("unknown csr address name: {}".format(addr)) return self.listeners[addr] = listener @@ -107,7 +118,7 @@ class CSR: new_val = erased | (val << off) self.set('mstatus', new_val) - def get_mstatus(self, name): + def get_mstatus(self, name) -> int: size = 2 if name in MSTATUS_LEN_2 else 1 off = MSTATUS_OFFSETS[name] mask = (2**size - 1) << off @@ -134,3 +145,14 @@ class CSR: return None return self.name_to_addr[addr] return addr + + def virtual_register(self, addr: Union[str, int]): + addr = self._addr_to_name(addr) + if addr is None: + print("unknown csr address name: {}".format(addr)) + + def inner(func: Callable[[], int]): + self.virtual_regs[addr] = func + return func + + return inner -- 2.40.1 From 85af9b992f3a9e6c30dca506473c193aa1406b9a Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 25 May 2021 23:50:38 +0200 Subject: [PATCH 24/69] [PrivCPU] overhaul of instruction cycle, adding more CSR interaction --- riscemu/priv/Exceptions.py | 9 +++ riscemu/priv/PrivCPU.py | 141 ++++++++++++++++++++++--------------- 2 files changed, 93 insertions(+), 57 deletions(-) diff --git a/riscemu/priv/Exceptions.py b/riscemu/priv/Exceptions.py index 37388ad..60e653a 100644 --- a/riscemu/priv/Exceptions.py +++ b/riscemu/priv/Exceptions.py @@ -3,8 +3,17 @@ from typing import Optional class CpuTrap(BaseException): code: int + """ + 31-bit value encoding the exception code in the mstatus register + """ interrupt: int + """ + The isInterrupt bit in the mstatus register + """ mtval: int + """ + contents of the mtval register + """ def __init__(self, interrupt: int, code: int, mtval=0): assert 0 <= interrupt <= 1 diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 43fd4df..6654b92 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -3,21 +3,16 @@ RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ +import time from riscemu.CPU import * -from enum import IntEnum -from riscemu.Executable import LoadedMemorySection -from .Exceptions import * from .CSR import CSR -from .PrivRV32I import PrivRV32I -from ..instructions.RV32M import RV32M -from .PrivMMU import PrivMMU from .ElfLoader import ElfExecutable +from .Exceptions import * +from .PrivMMU import PrivMMU +from .PrivRV32I import PrivRV32I from .privmodes import PrivModes - -from collections import defaultdict - -from typing import Union +from ..instructions.RV32M import RV32M if typing.TYPE_CHECKING: from riscemu import Executable, LoadedExecutable, LoadedInstruction @@ -33,6 +28,14 @@ class PrivCPU(CPU): """ csr: CSR + """ + Reference to the control and status registers + """ + + TIME_RESOLUTION_NS: int = 1000000 + """ + controls the resolution of the time csr register (in nanoseconds) + """ INS_XLEN = 4 """ @@ -49,6 +52,14 @@ class PrivCPU(CPU): self.pc = kernel.run_ptr self.syscall_int = None + self.launch_debug = False + + self.pending_traps: List[CpuTrap] = list() + + self._time_start = 0 + self._time_timecmp = 0 + self._time_interrupt_enabled = False + # init csr self._init_csr() @@ -58,59 +69,23 @@ class PrivCPU(CPU): ins = None try: while not self.exit: - try: - 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.run_instruction(ins) - self.pc += self.INS_XLEN - except CpuTrap as trap: - mie = self.csr.get_mstatus('mie') - if not mie: - print("Caught trap while mie={}!".format(mie)) - # TODO: handle this a lot better - continue - # raise trap - - # caught a trap! - self.csr.set('mepc', self.pc) # store MEPC - self.csr.set_mstatus('mpp', self.mode) # save mpp - self.csr.set_mstatus('mpie', mie) # save mie - self.csr.set_mstatus('mie', 0) # disable further interrupts - self.csr.set('mcause', trap.mcause) # store cause - - # set mtval csr - self.csr.set('mtval', trap.mtval) - - # set priv mode to machine - self.mode = PrivModes.MACHINE - - # trap vector - mtvec = self.csr.get('mtvec') - if mtvec & 3 == 1: - # vectored mode! - self.pc = (mtvec >> 2) + (self.INS_XLEN * trap.code) - else: - # standard mode - self.pc = (mtvec >> 2) + self.step(verbose=False) except RiscemuBaseException as ex: - if not isinstance(ex, LaunchDebuggerException): + if isinstance(ex, LaunchDebuggerException): + self.launch_debug = True + else: print(FMT_ERROR + "[CPU] excpetion caught at 0x{:08X}: {}:".format(self.pc - 1, ins) + FMT_NONE) print(ex.message()) - self.pc -= 1 - - 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.conf.debug_on_exception: + self.launch_debug = True if self.exit: print() print(FMT_CPU + "Program exited with code {}".format(self.exit_code) + FMT_NONE) sys.exit(self.exit_code) + elif self.launch_debug: + launch_debug_session(self, self.mmu, self.regs, + "Launching debugger:") + self._run(verbose) else: print() print(FMT_CPU + "Program stopped without exiting - perhaps you stopped the debugger?" + FMT_NONE) @@ -126,6 +101,7 @@ class PrivCPU(CPU): def run(self): print(FMT_CPU + '[CPU] Started running from 0x{:08X} ({})'.format(self.pc, "kernel") + FMT_NONE) + self._time_start = time.perf_counter_ns() // self.TIME_RESOLUTION_NS self._run(True) def _init_csr(self): @@ -133,10 +109,12 @@ class PrivCPU(CPU): self.csr = CSR() self.csr.set('mhartid', 0) # core id # TODO: set correct value - self.csr.set('mimpid', 1) # implementation id + self.csr.set('mimpid', 0) # implementation id # set mxl to 1 (32 bit) and set bits for i and m isa self.csr.set('misa', (1 << 30) + (1 << 8) + (1 << 12)) # available ISA + # CSR write callbacks: + @self.csr.callback('halt') def halt(old: int, new: int): if new != 0: @@ -146,3 +124,52 @@ class PrivCPU(CPU): @self.csr.callback('mstatus') def mstatus(old: int, new: int): pass + + @self.csr.callback('mtimecmp') + def mtimecmp(old, new): + self._time_timecmp = (self.csr.get('mtimecmph') << 32) + new + self._time_interrupt_enabled = True + + @self.csr.callback('mtimecmph') + def mtimecmp(old, new): + self._time_timecmp = (new << 32) + self.csr.get('mtimecmp') + self._time_interrupt_enabled = True + + # virtual CSR registers: + + @self.csr.virtual_register('time') + def get_time(): + return (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) & (2 ** 32 - 1) + + @self.csr.virtual_register('timeh') + def get_timeh(): + return (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) >> 32 + + # add minstret and mcycle counters + + def _handle_trap(self, trap: CpuTrap): + # implement trap handling! + self.pending_traps.append(trap) + + def step(self, verbose = True): + try: + self.cycle += 1 + self._timer_step() + self._check_interrupt() + ins = self.mmu.read_ins(self.pc) + if verbose: + print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins)) + self.run_instruction(ins) + self.pc += self.INS_XLEN + except CpuTrap as trap: + self._handle_trap(trap) + + def _timer_step(self): + if not self._time_interrupt_enabled: + return + if self._time_timecmp < (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start: + self.pending_traps.append(CpuTrap(1, 7, 0)) + self._time_interrupt_enabled = False + + def _check_interrupt(self): + pass -- 2.40.1 From c963fe39896fb12e1dbad2833b34dfa9bfa56617 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Wed, 26 May 2021 18:40:42 +0200 Subject: [PATCH 25/69] [Priv] small fixes for overlooked things --- riscemu/priv/CSR.py | 5 ++++- riscemu/priv/PrivCPU.py | 3 ++- riscemu/priv/PrivRV32I.py | 1 - 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index 32bbb97..b7b007e 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -2,6 +2,7 @@ from typing import Dict, Union, Callable, Optional from collections import defaultdict from .privmodes import PrivModes from .Exceptions import IllegalInstructionTrap +from ..helpers import to_unsigned MSTATUS_OFFSETS = { 'uie': 0, @@ -74,12 +75,14 @@ class CSR: def __init__(self): self.regs = defaultdict(lambda: 0) self.listeners = defaultdict(lambda: (lambda x, y: None)) + self.virtual_regs = dict() #TODO: implement write masks (bitmasks which control writeable bits in registers def set(self, addr: Union[str, int], val: int): addr = self._addr_to_name(addr) if addr is None: return + val = to_unsigned(val) self.listeners[addr](self.regs[addr], val) self.regs[addr] = val @@ -135,7 +138,7 @@ class CSR: raise IllegalInstructionTrap() def assert_can_write(self, mode: PrivModes, addr: int): - if (addr >> 8) & 3 > mode.value() or addr >> 10 == 11: + if (addr >> 8) & 3 > mode.value or addr >> 10 == 11: raise IllegalInstructionTrap() def _addr_to_name(self, addr: Union[str, int]) -> Optional[int]: diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 6654b92..fe0df86 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -69,10 +69,11 @@ class PrivCPU(CPU): ins = None try: while not self.exit: - self.step(verbose=False) + self.step(verbose) except RiscemuBaseException as ex: if isinstance(ex, LaunchDebuggerException): self.launch_debug = True + self.pc += self.INS_XLEN else: print(FMT_ERROR + "[CPU] excpetion caught at 0x{:08X}: {}:".format(self.pc - 1, ins) + FMT_NONE) print(ex.message()) diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index f0b5c23..1c17791 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -130,5 +130,4 @@ class PrivRV32I(RV32I): def parse_mem_ins(self, ins: 'LoadedInstruction') -> Tuple[str, int]: ASSERT_LEN(ins.args, 3) - print("dop") return ins.get_reg(1), self.get_reg_content(ins, 0) + ins.get_imm(2) -- 2.40.1 From de261c4c437b642ef77178bc8495caf7ea76b630 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Fri, 4 Jun 2021 20:36:33 +0200 Subject: [PATCH 26/69] [Priv] overhauled instruction architecture --- riscemu/priv/CSR.py | 2 +- riscemu/priv/Exceptions.py | 41 ++++++++++++++++++++++++++++++-------- riscemu/priv/PrivCPU.py | 2 +- riscemu/priv/PrivRV32I.py | 12 +++++------ 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index b7b007e..2b1b1c6 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -1,7 +1,7 @@ from typing import Dict, Union, Callable, Optional from collections import defaultdict from .privmodes import PrivModes -from .Exceptions import IllegalInstructionTrap +from .Exceptions import InstructionAccessFault from ..helpers import to_unsigned MSTATUS_OFFSETS = { diff --git a/riscemu/priv/Exceptions.py b/riscemu/priv/Exceptions.py index 60e653a..fd0dac8 100644 --- a/riscemu/priv/Exceptions.py +++ b/riscemu/priv/Exceptions.py @@ -1,5 +1,16 @@ -from typing import Optional +from typing import Optional, NewType +from enum import Enum +from .privmodes import PrivModes +import typing +if typing.TYPE_CHECKING: + from .ElfLoader import ElfInstruction + +class CpuTrapType(Enum): + TIMER = 1 + SOFTWARE = 2 + EXTERNAL = 3 + EXCEPTION = 4 class CpuTrap(BaseException): code: int @@ -10,17 +21,28 @@ class CpuTrap(BaseException): """ The isInterrupt bit in the mstatus register """ + mtval: int """ contents of the mtval register """ - def __init__(self, interrupt: int, code: int, mtval=0): - assert 0 <= interrupt <= 1 + type: CpuTrapType + """ + The type (timer, external, software) of the trap + """ + + priv: PrivModes + """ + The privilege level this trap targets + """ - self.interrupt = interrupt + def __init__(self, code: int, mtval, type: CpuTrapType, priv: PrivModes = PrivModes.MACHINE): + self.interrupt = 0 if type == CpuTrapType.EXCEPTION else 1 self.code = code self.mtval = mtval + self.priv = priv + self.type = type @property def mcause(self): @@ -28,17 +50,20 @@ class CpuTrap(BaseException): class IllegalInstructionTrap(CpuTrap): - def __init__(self): - super().__init__(0, 2, 0) + def __init__(self, ins: 'ElfInstruction'): + super().__init__(2, ins.encoded, CpuTrapType.EXCEPTION) class InstructionAddressMisalignedTrap(CpuTrap): def __init__(self, addr: int): - super().__init__(0, 0, addr) + super().__init__(0, addr, CpuTrapType.EXCEPTION) class InstructionAccessFault(CpuTrap): def __init__(self, addr: int): - super().__init__(0, 1, addr) + super().__init__(1, addr, CpuTrapType.EXCEPTION) +class TimerInterrupt(CpuTrap): + def __init(self): + super().__init__(7, 0, CpuTrapType.TIMER) diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index fe0df86..ed5d3db 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -169,7 +169,7 @@ class PrivCPU(CPU): if not self._time_interrupt_enabled: return if self._time_timecmp < (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start: - self.pending_traps.append(CpuTrap(1, 7, 0)) + self.pending_traps.append(TimerInterrupt()) self._time_interrupt_enabled = False def _check_interrupt(self): diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index 1c17791..be808f6 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -49,7 +49,7 @@ class PrivRV32I(RV32I): def instruction_mret(self, ins: 'LoadedInstruction'): if self.cpu.mode != PrivModes.MACHINE: print("MRET not inside machine level code!") - raise IllegalInstructionTrap() + raise IllegalInstructionTrap(ins) # retore mie mpie = self.cpu.csr.get_mstatus('mpie') self.cpu.csr.set_mstatus('mie', mpie) @@ -61,21 +61,21 @@ class PrivRV32I(RV32I): self.cpu.pc = mepc def instruction_uret(self, ins: 'LoadedInstruction'): - raise IllegalInstructionTrap() + raise IllegalInstructionTrap(ins) def instruction_sret(self, ins: 'LoadedInstruction'): - raise IllegalInstructionTrap() + raise IllegalInstructionTrap(ins) def instruction_scall(self, ins: 'LoadedInstruction'): """ Overwrite the scall from userspace RV32I """ if self.cpu.mode == PrivModes.USER: - raise CpuTrap(0, 8) # ecall from U mode + raise CpuTrap(8, 0, CpuTrapType.SOFTWARE, self.cpu.mode) # ecall from U mode elif self.cpu.mode == PrivModes.SUPER: - raise CpuTrap(0, 9) # ecall from S mode - should not happen + raise CpuTrap(9, 0, CpuTrapType.SOFTWARE, self.cpu.mode) # ecall from S mode - should not happen elif self.cpu.mode == PrivModes.MACHINE: - raise CpuTrap(0, 11) # ecall from M mode + raise CpuTrap(11, 0, CpuTrapType.SOFTWARE, self.cpu.mode) # ecall from M mode def instruction_beq(self, ins: 'LoadedInstruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins) -- 2.40.1 From 79369889f4cf291d901b1939027841f0dc7d97a8 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Fri, 4 Jun 2021 20:37:08 +0200 Subject: [PATCH 27/69] [CSR] fixed method naming for _addr_to_name (now _name_to_addr) --- riscemu/priv/CSR.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index 2b1b1c6..a03d46f 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -79,7 +79,7 @@ class CSR: #TODO: implement write masks (bitmasks which control writeable bits in registers def set(self, addr: Union[str, int], val: int): - addr = self._addr_to_name(addr) + addr = self._name_to_addr(addr) if addr is None: return val = to_unsigned(val) @@ -87,15 +87,15 @@ class CSR: self.regs[addr] = val def get(self, addr: Union[str, int]) -> int: - addr = self._addr_to_name(addr) + addr = self._name_to_addr(addr) if addr is None: - return + raise RuntimeError(f"Invalid CSR name: {addr}!") if addr in self.virtual_regs: return self.virtual_regs[addr]() return self.regs[addr] def set_listener(self, addr: Union[str, int], listener: Callable[[int, int], None]): - addr = self._addr_to_name(addr) + addr = self._name_to_addr(addr) if addr is None: print("unknown csr address name: {}".format(addr)) return @@ -135,13 +135,13 @@ class CSR: def assert_can_read(self, mode: PrivModes, addr: int): if (addr >> 8) & 3 > mode.value(): - raise IllegalInstructionTrap() + raise InstructionAccessFault(addr) def assert_can_write(self, mode: PrivModes, addr: int): if (addr >> 8) & 3 > mode.value or addr >> 10 == 11: - raise IllegalInstructionTrap() + raise InstructionAccessFault(addr) - def _addr_to_name(self, addr: Union[str, int]) -> Optional[int]: + def _name_to_addr(self, addr: Union[str, int]) -> Optional[int]: if isinstance(addr, str): if addr not in self.name_to_addr: print("Unknown CSR register {}".format(addr)) @@ -150,7 +150,7 @@ class CSR: return addr def virtual_register(self, addr: Union[str, int]): - addr = self._addr_to_name(addr) + addr = self._name_to_addr(addr) if addr is None: print("unknown csr address name: {}".format(addr)) -- 2.40.1 From ca3b4099d459a5a66dd02638377a2d6c15cfb39a Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sat, 5 Jun 2021 09:27:03 +0200 Subject: [PATCH 28/69] [Priv] moved CSR constants to a separate file --- riscemu/priv/CSR.py | 56 ++------------------------- riscemu/priv/CSRConsts.py | 81 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 53 deletions(-) create mode 100644 riscemu/priv/CSRConsts.py diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index a03d46f..d9a7d00 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -3,35 +3,7 @@ from collections import defaultdict from .privmodes import PrivModes from .Exceptions import InstructionAccessFault from ..helpers import to_unsigned - -MSTATUS_OFFSETS = { - '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 -""" - +from .CSRConsts import CSR_NAME_TO_ADDR, MSTATUS_LEN_2, MSTATUS_OFFSETS class CSR: """ @@ -47,28 +19,6 @@ class CSR: list of virtual CSR registers, with values computed on read """ - name_to_addr: Dict[str, int] = { - 'mstatus': 0x300, - 'misa': 0x301, - 'mie': 0x304, - 'mtvec': 0x305, - 'mepc': 0x341, - 'mcause': 0x342, - 'mtval': 0x343, - 'mip': 0x344, - 'mvendorid': 0xF11, - 'marchid': 0xF12, - 'mimpid': 0xF13, - 'mhartid': 0xF14, - 'time': 0xc01, - 'timeh': 0xc81, - 'halt': 0x789, - 'mtimecmp': 0x780, - 'mtimecmph': 0x781, - } - """ - Translation for named registers - """ listeners: Dict[int, Callable[[int, int], None]] @@ -143,10 +93,10 @@ class CSR: def _name_to_addr(self, addr: Union[str, int]) -> Optional[int]: if isinstance(addr, str): - if addr not in self.name_to_addr: + if addr not in CSR_NAME_TO_ADDR: print("Unknown CSR register {}".format(addr)) return None - return self.name_to_addr[addr] + return CSR_NAME_TO_ADDR[addr] return addr def virtual_register(self, addr: Union[str, int]): diff --git a/riscemu/priv/CSRConsts.py b/riscemu/priv/CSRConsts.py new file mode 100644 index 0000000..b8b75bb --- /dev/null +++ b/riscemu/priv/CSRConsts.py @@ -0,0 +1,81 @@ +from typing import Dict, Tuple + +MCAUSE_TRANSLATION: Dict[Tuple[int, int], str]= { + (1, 0): 'User software interrupt', + (1, 1): 'Supervisor software interrupt', + (1, 3): 'Machine software interrupt', + (1, 4): 'User timer interrupt', + (1, 5): 'Supervisor timer interrupt', + (1, 7): 'Machine timer interrupt', + (1, 8): 'User external interrupt', + (1, 9): 'Supervisor external interrupt', + (1, 11): 'Machine external interrupt', + (0, 0): 'Instruction address misaligned', + (0, 1): 'Instruction access fault', + (0, 2): 'Illegal instruction', + (0, 3): 'Breakpoint', + (0, 4): 'Load address misaligned', + (0, 5): 'Load access fault', + (0, 6): 'Store/AMO address misaligned', + (0, 7): 'Store/AMO access fault', + (0, 8): 'environment call from user mode', + (0, 9): 'environment call from supervisor mode', + (0, 11): 'environment call from machine mode', + (0, 12): 'Instruction page fault', + (0, 13): 'Load page fault', + (0, 15): 'Store/AMO page fault', +} +""" +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] = { + 'mstatus': 0x300, + 'misa': 0x301, + 'mie': 0x304, + 'mtvec': 0x305, + 'mepc': 0x341, + 'mcause': 0x342, + 'mtval': 0x343, + 'mip': 0x344, + 'mvendorid': 0xF11, + 'marchid': 0xF12, + 'mimpid': 0xF13, + 'mhartid': 0xF14, + 'time': 0xc01, + 'timeh': 0xc81, + 'halt': 0x789, + 'mtimecmp': 0x780, + 'mtimecmph': 0x781, +} +""" +Translation for named registers +""" \ No newline at end of file -- 2.40.1 From 198d14d5fbf86f9c4e0c1607e545be67845892f6 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sat, 5 Jun 2021 09:29:20 +0200 Subject: [PATCH 29/69] [Priv Exceptions] added __repr__ to CpuTrap class --- riscemu/priv/Exceptions.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/riscemu/priv/Exceptions.py b/riscemu/priv/Exceptions.py index fd0dac8..a060e79 100644 --- a/riscemu/priv/Exceptions.py +++ b/riscemu/priv/Exceptions.py @@ -1,17 +1,21 @@ from typing import Optional, NewType from enum import Enum from .privmodes import PrivModes +from .CSRConsts import MCAUSE_TRANSLATION import typing + if typing.TYPE_CHECKING: from .ElfLoader import ElfInstruction + class CpuTrapType(Enum): TIMER = 1 SOFTWARE = 2 EXTERNAL = 3 EXCEPTION = 4 + class CpuTrap(BaseException): code: int """ @@ -48,6 +52,16 @@ class CpuTrap(BaseException): def mcause(self): return (self.code << 31) + self.interrupt + def __repr__(self): + name = "Reserved interrupt({}, {})".format(self.interrupt, self.code) + + if (self.interrupt, self.code) in MCAUSE_TRANSLATION: + name = MCAUSE_TRANSLATION[(self.interrupt, self.code)] + "({}, {})".format(self.interrupt, self.code) + + return "{} {{priv={}, type={}, mtval={}}}".format( + name, self.priv.name, self.type.name, self.mtval + ) + class IllegalInstructionTrap(CpuTrap): def __init__(self, ins: 'ElfInstruction'): -- 2.40.1 From 9424390b656f982849bb8bf99a963aa087bf94fa Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sat, 5 Jun 2021 09:54:58 +0200 Subject: [PATCH 30/69] [decoder] Added mret, sret, uret, wfi instruction decoding support --- riscemu/decoder/decoder.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/riscemu/decoder/decoder.py b/riscemu/decoder/decoder.py index b218ccd..f0601e8 100644 --- a/riscemu/decoder/decoder.py +++ b/riscemu/decoder/decoder.py @@ -15,6 +15,10 @@ STATIC_INSN: Dict[int, Tuple[str, List[Union[str, int]], int]] = { 0x02010113: ("addi", ["sp", "sp", 32], 0x02010113), 0x00100073: ("ebreak", [], 0x00100073), 0x00000073: ("ecall", [], 0x00000073), + 0x30200073: ("mret", [], 0x30200073), + 0x00200073: ("uret", [], 0x00200073), + 0x10200073: ("sret", [], 0x10200073), + 0x10500073: ("wfi", [], 0x10500073), } -- 2.40.1 From f9b0bac2452121e3d77ccc4721595b0b4dbf8255 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sat, 5 Jun 2021 09:56:05 +0200 Subject: [PATCH 31/69] [Priv Exceptions] fixed constructor typo in TimerInterrupt --- riscemu/priv/Exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/riscemu/priv/Exceptions.py b/riscemu/priv/Exceptions.py index a060e79..e2b15fe 100644 --- a/riscemu/priv/Exceptions.py +++ b/riscemu/priv/Exceptions.py @@ -79,5 +79,5 @@ class InstructionAccessFault(CpuTrap): class TimerInterrupt(CpuTrap): - def __init(self): + def __init__(self): super().__init__(7, 0, CpuTrapType.TIMER) -- 2.40.1 From 849d5f4fc3aa13e755ee59c2ed0898401a6457be Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sat, 5 Jun 2021 15:24:16 +0200 Subject: [PATCH 32/69] [decoder, ElfLoader] decoing an instruction now returns all args as int --- riscemu/decoder/__init__.py | 2 +- riscemu/decoder/__main__.py | 3 ++- riscemu/decoder/decoder.py | 8 ++++---- riscemu/decoder/formats.py | 30 +++++++++++++++--------------- riscemu/priv/ElfLoader.py | 14 +++++++------- 5 files changed, 29 insertions(+), 28 deletions(-) diff --git a/riscemu/decoder/__init__.py b/riscemu/decoder/__init__.py index 3ba852e..e68396a 100644 --- a/riscemu/decoder/__init__.py +++ b/riscemu/decoder/__init__.py @@ -1 +1 @@ -from .decoder import decode \ No newline at end of file +from .decoder import decode, RISCV_REGS diff --git a/riscemu/decoder/__main__.py b/riscemu/decoder/__main__.py index e109c97..98bc146 100644 --- a/riscemu/decoder/__main__.py +++ b/riscemu/decoder/__main__.py @@ -6,6 +6,7 @@ if __name__ == '__main__': from .decoder import * from .formats import * from .instruction_table import * + from .regs import RISCV_REGS sess_vars = globals() sess_vars.update(locals()) @@ -13,4 +14,4 @@ if __name__ == '__main__': readline.set_completer(rlcompleter.Completer(sess_vars).complete) readline.set_completer(rlcompleter.Completer(sess_vars).complete) readline.parse_and_bind("tab: complete") - code.InteractiveConsole(sess_vars).interact(banner="Interaktive decoding session started...", exitmsg="Closing...") \ No newline at end of file + code.InteractiveConsole(sess_vars).interact(banner="Interaktive decoding session started...", exitmsg="Closing...") diff --git a/riscemu/decoder/decoder.py b/riscemu/decoder/decoder.py index f0601e8..5b198ad 100644 --- a/riscemu/decoder/decoder.py +++ b/riscemu/decoder/decoder.py @@ -8,11 +8,11 @@ def print_ins(ins: int): f"0b{ins >> 25 :07b}_{(ins >> 20) & 0b11111:05b}_{(ins >> 15) & 0b11111:03b}_{(ins >> 12) & 0b111:03b}_{(ins >> 7) & 0b11111:05b}_{ins & 0b1111111:07b}"); -STATIC_INSN: Dict[int, Tuple[str, List[Union[str, int]], int]] = { +STATIC_INSN: Dict[int, Tuple[str, List[int], int]] = { 0x00000013: ("nop", [], 0x00000013), 0x00008067: ("ret", [], 0x00008067), - 0xfe010113: ("addi", ["sp", "sp", -32], 0xfe010113), - 0x02010113: ("addi", ["sp", "sp", 32], 0x02010113), + 0xfe010113: ("addi", [2, 2, -32], 0xfe010113), + 0x02010113: ("addi", [2, 2, 32], 0x02010113), 0x00100073: ("ebreak", [], 0x00100073), 0x00000073: ("ecall", [], 0x00000073), 0x30200073: ("mret", [], 0x30200073), @@ -72,7 +72,7 @@ def name_from_insn(ins: int): raise RuntimeError(f"Invalid instruction: {ins:x}") -def decode(ins: bytearray) -> Tuple[str, List[Union[str, int]], int]: +def decode(ins: bytearray) -> Tuple[str, List[int], int]: insn = int_from_ins(ins) if insn & 3 != 3: diff --git a/riscemu/decoder/formats.py b/riscemu/decoder/formats.py index ccca92f..0b67d6a 100644 --- a/riscemu/decoder/formats.py +++ b/riscemu/decoder/formats.py @@ -69,37 +69,37 @@ def sign_extend(num, bits): return (num & (sign_mask - 1)) - (num & sign_mask) -def decode_i(ins: int) -> List[Union[str, int]]: - return [RISCV_REGS[rd(ins)], RISCV_REGS[rs1(ins)], imm_i(ins)] +def decode_i(ins: int) -> List[int]: + return [rd(ins), rs1(ins), imm_i(ins)] -def decode_b(ins: int) -> List[Union[str, int]]: - return [RISCV_REGS[rs1(ins)], RISCV_REGS[rs2(ins)], imm_b(ins)] +def decode_b(ins: int) -> List[int]: + return [rs1(ins), rs2(ins), imm_b(ins)] -def decode_u(ins: int) -> List[Union[str, int]]: - return [RISCV_REGS[rd(ins)], imm_u(ins)] +def decode_u(ins: int) -> List[int]: + return [rd(ins), imm_u(ins)] -def decode_r(ins: int) -> List[Union[str, int]]: - return [RISCV_REGS[rd(ins)], RISCV_REGS[rs1(ins)], RISCV_REGS[rs2(ins)]] +def decode_r(ins: int) -> List[int]: + return [rd(ins), rs1(ins), rs2(ins)] -def decode_s(ins: int) -> List[Union[str, int]]: - return [RISCV_REGS[rs1(ins)], RISCV_REGS[rs2(ins)], imm_s(ins)] +def decode_s(ins: int) -> List[int]: + return [rs2(ins), rs1(ins), imm_s(ins)] -def decode_j(ins: int) -> List[Union[str, int]]: - return [RISCV_REGS[rd(ins)], imm_j(ins)] +def decode_j(ins: int) -> List[int]: + return [rd(ins), imm_j(ins)] -def decode_i_shamt(ins: int) -> List[Union[str, int]]: +def decode_i_shamt(ins: int) -> List[int]: if funct3(ins) in (1, 5): - return [RISCV_REGS[rd(ins)], RISCV_REGS[rs1(ins)], rs2(ins)] + return [rd(ins), rs1(ins), rs2(ins)] return decode_i(ins) -INSTRUCTION_ARGS_DECODER: Dict[int, Callable[[int], List[Union[str, int]]]] = { +INSTRUCTION_ARGS_DECODER: Dict[int, Callable[[int], List[int]]] = { 0x00: decode_i, 0x04: decode_i_shamt, 0x05: decode_u, diff --git a/riscemu/priv/ElfLoader.py b/riscemu/priv/ElfLoader.py index 09ac73d..cf58bea 100644 --- a/riscemu/priv/ElfLoader.py +++ b/riscemu/priv/ElfLoader.py @@ -7,7 +7,7 @@ from elftools.elf.sections import Section, SymbolTableSection from .Exceptions import * from ..Exceptions import RiscemuBaseException from ..Executable import MemoryFlags, LoadedMemorySection -from ..decoder import decode +from ..decoder import decode, RISCV_REGS from ..helpers import FMT_PARSE, FMT_NONE, FMT_GREEN, FMT_BOLD FMT_ELF = FMT_GREEN + FMT_BOLD @@ -96,17 +96,17 @@ class InvalidElfException(RiscemuBaseException): @dataclass(frozen=True) class ElfInstruction: name: str - args: List[Union[int, str]] + args: List[int] encoded: int - def get_imm(self, num: int): - return self.args[-1] + def get_imm(self, num: int) -> int: + return self.args[num] - def get_imm_reg(self, num: int): + def get_imm_reg(self, num: int) -> Tuple[int, int]: return self.args[-1], self.args[-2] - def get_reg(self, num: int): - return self.args[num] + def get_reg(self, num: int) -> str: + return RISCV_REGS[self.args[num]] def __repr__(self): if self.name in ('sw', 'sh', 'sb', 'lb', 'lh', 'lb', 'lbu', 'lhu'): -- 2.40.1 From 3e4920f5d951f547d9a9a5fe353e64999da5133e Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sat, 5 Jun 2021 15:24:40 +0200 Subject: [PATCH 33/69] [decoder] fixed bug when decoding add/sub instruction --- riscemu/decoder/decoder.py | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/riscemu/decoder/decoder.py b/riscemu/decoder/decoder.py index 5b198ad..9219055 100644 --- a/riscemu/decoder/decoder.py +++ b/riscemu/decoder/decoder.py @@ -39,16 +39,16 @@ def name_from_insn(ins: int): if isinstance(dec, str): return dec - fun = funct3(ins) - if fun not in dec: + fun3 = funct3(ins) + if fun3 not in dec: print_ins(ins) - raise RuntimeError(f"Invalid funct3: {fun:0x} in insn {ins:x}") + raise RuntimeError(f"Invalid funct3: {fun3:0x} in insn {ins:x}") - dec = dec[fun] + dec = dec[fun3] if isinstance(dec, str): return dec - if opcode == 0x1c and fun == 0: + if opcode == 0x1c and fun3 == 0: # we have ecall/ebreak token = imm110(ins) if token in dec: @@ -56,17 +56,13 @@ def name_from_insn(ins: int): print_ins(ins) raise RuntimeError(f"Invalid instruction in ebreak/ecall region: {ins:x}") - fun = funct7(ins) - if fun in dec: - if opcode == 0x0C or (opcode == 0x04 and fun == 5): - mode = imm110(ins) - dec = dec[fun] - if mode in dec: - return dec[mode] - print_ins(ins) - raise RuntimeError("Unknown instruction!") - - return dec[fun] + fun7 = funct7(ins) + if fun7 in dec: + if opcode == 0x0C or (opcode == 0x04 and fun3 == 5): + dec = dec[fun7] + return dec + print("unknown instruction?!") + return dec[fun7] print_ins(ins) raise RuntimeError(f"Invalid instruction: {ins:x}") -- 2.40.1 From c770cc05cffce4cd1fbbabf4ba66b8d4e42dd4e8 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sat, 5 Jun 2021 15:25:39 +0200 Subject: [PATCH 34/69] [Priv Exceptions] added __str__ as __repr__ alias to CpuTrap to correctly format exceptions when printed --- riscemu/priv/Exceptions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/riscemu/priv/Exceptions.py b/riscemu/priv/Exceptions.py index e2b15fe..db3a544 100644 --- a/riscemu/priv/Exceptions.py +++ b/riscemu/priv/Exceptions.py @@ -62,6 +62,9 @@ class CpuTrap(BaseException): name, self.priv.name, self.type.name, self.mtval ) + def __str__(self): + return self.__repr__() + class IllegalInstructionTrap(CpuTrap): def __init__(self, ins: 'ElfInstruction'): -- 2.40.1 From e4537f86d9f5bc9bf9b8682c3482355ff7cf4cbc Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sat, 5 Jun 2021 15:28:27 +0200 Subject: [PATCH 35/69] [PrivRV32I] implemented csrrwi instruction --- riscemu/priv/PrivRV32I.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index be808f6..201807e 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -41,7 +41,15 @@ class PrivRV32I(RV32I): INS_NOT_IMPLEMENTED(ins) def instruction_csrrwi(self, ins: 'LoadedInstruction'): - INS_NOT_IMPLEMENTED(ins) + ASSERT_LEN(ins.args, 3) + rd, imm, addr = ins.get_reg(0), ins.get_imm(1), ins.get_imm(2) + if rd != 'zero': + self.cpu.csr.assert_can_read(self.cpu.mode, addr) + old_val = self.cpu.csr.get(addr) + self.regs.set(rd, old_val) + self.cpu.csr.assert_can_write(self.cpu.mode, addr) + self.cpu.csr.set(addr, imm) + def instruction_csrrci(self, ins: 'LoadedInstruction'): INS_NOT_IMPLEMENTED(ins) -- 2.40.1 From 37910018b9b724c7f0e9006cf059bade86599360 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sat, 5 Jun 2021 15:29:06 +0200 Subject: [PATCH 36/69] [PrivRV32I] finally correct parsing of load/store instruction args --- riscemu/priv/PrivRV32I.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index 201807e..2be49cb 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -138,4 +138,6 @@ class PrivRV32I(RV32I): def parse_mem_ins(self, ins: 'LoadedInstruction') -> Tuple[str, int]: ASSERT_LEN(ins.args, 3) - return ins.get_reg(1), self.get_reg_content(ins, 0) + ins.get_imm(2) + addr = self.get_reg_content(ins, 1) + ins.get_imm(2) + reg = ins.get_reg(0) + return reg, addr -- 2.40.1 From c1110b9ce3c2116368088fd878d31dd4a10dcf33 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sat, 5 Jun 2021 15:29:40 +0200 Subject: [PATCH 37/69] [ElfLoader] better formatting for jump and load/store instructions --- riscemu/priv/ElfLoader.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/riscemu/priv/ElfLoader.py b/riscemu/priv/ElfLoader.py index cf58bea..f908f4d 100644 --- a/riscemu/priv/ElfLoader.py +++ b/riscemu/priv/ElfLoader.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import List, Dict, Union +from typing import List, Dict, Tuple from elftools.elf.elffile import ELFFile from elftools.elf.sections import Section, SymbolTableSection @@ -108,10 +108,12 @@ class ElfInstruction: def get_reg(self, num: int) -> str: return RISCV_REGS[self.args[num]] - def __repr__(self): - if self.name in ('sw', 'sh', 'sb', 'lb', 'lh', 'lb', 'lbu', 'lhu'): + def __repr__(self) -> str: + if self.name == 'jal' and self.args[0] == 0: + return "j {}".format(self.args[1]) + elif self.name in ('lw', 'lh', 'lb', 'lbu', 'lhu', 'sw', 'sh', 'sb'): args = "{}, {}({})".format( - self.args[1], self.args[2], self.args[0] + RISCV_REGS[self.args[0]], self.args[2], RISCV_REGS[self.args[1]] ) else: args = ", ".join(map(str, self.args)) -- 2.40.1 From f14bd2b98335d8a2f669e80ec3db4d66b6467cee Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sat, 5 Jun 2021 16:19:35 +0200 Subject: [PATCH 38/69] [PrivCPU, PrivRV32I] fixed bug where ebreaks where missed during debugging --- riscemu/priv/PrivCPU.py | 4 +++- riscemu/priv/PrivRV32I.py | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index ed5d3db..ef34bf0 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -18,6 +18,7 @@ if typing.TYPE_CHECKING: from riscemu import Executable, LoadedExecutable, LoadedInstruction from riscemu.instructions.InstructionSet import InstructionSet + class PrivCPU(CPU): """ This is a CPU that has different modes, instruction sets and registers. @@ -84,6 +85,7 @@ class PrivCPU(CPU): print(FMT_CPU + "Program exited with code {}".format(self.exit_code) + FMT_NONE) sys.exit(self.exit_code) elif self.launch_debug: + self.launch_debug = False launch_debug_session(self, self.mmu, self.regs, "Launching debugger:") self._run(verbose) @@ -152,7 +154,7 @@ class PrivCPU(CPU): # implement trap handling! self.pending_traps.append(trap) - def step(self, verbose = True): + def step(self, verbose=True): try: self.cycle += 1 self._timer_step() diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index 2be49cb..685edf1 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -132,6 +132,9 @@ class PrivRV32I(RV32I): self.regs.set(rd, self.pc) self.pc = rs + imm - 4 + def instruction_sbreak(self, ins: 'LoadedInstruction'): + raise LaunchDebuggerException() + def parse_crs_ins(self, ins: 'LoadedInstruction'): ASSERT_LEN(ins.args, 3) return ins.get_reg(0), ins.get_reg(1), ins.get_imm(2) -- 2.40.1 From 6351f1e84db2ad297c9ab5ada201d6189d75409c Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sun, 6 Jun 2021 09:55:15 +0200 Subject: [PATCH 39/69] [PrivRV32I] fixed bug with blt backwards jumps missing by one --- riscemu/priv/PrivRV32I.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index 685edf1..c30cb30 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -98,7 +98,7 @@ class PrivRV32I(RV32I): def instruction_blt(self, ins: 'LoadedInstruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 < rs2: - self.pc += dst + self.pc += dst - 4 def instruction_bge(self, ins: 'LoadedInstruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins) -- 2.40.1 From 9278235e4433fcaa342c657f42c4e8425e7811a0 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 00:18:44 +0200 Subject: [PATCH 40/69] [decoder] fixed botched j immediate decoding --- riscemu/decoder/formats.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/riscemu/decoder/formats.py b/riscemu/decoder/formats.py index 0b67d6a..296e6ca 100644 --- a/riscemu/decoder/formats.py +++ b/riscemu/decoder/formats.py @@ -55,12 +55,11 @@ def imm_u(ins: int): def imm_j(ins: int): - imm = ins >> 12 return sign_extend( - ((imm >> 8) & 0b1111111111) + - ((imm & 1) << 10) + - ((imm & 0b11111111) << 11) + - (imm & 0b10000000000000000000), 20 + (((ins >> 21) & 0b1111111111) << 1) + + (((ins >> 20) & 1) << 11) + + (((ins >> 12) & 0b11111111) << 12) + + (((ins >> 31) & 1) << 20), 21 ) -- 2.40.1 From 79d913baaf6455c8dfb3ef0343e8803af86fde34 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 00:19:04 +0200 Subject: [PATCH 41/69] [decoder] fixed formatting in print_ins function --- riscemu/decoder/decoder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/riscemu/decoder/decoder.py b/riscemu/decoder/decoder.py index 9219055..462add4 100644 --- a/riscemu/decoder/decoder.py +++ b/riscemu/decoder/decoder.py @@ -5,7 +5,7 @@ from typing import Tuple, List def print_ins(ins: int): print(" f7 rs2 rs1 f3 rd op") print( - f"0b{ins >> 25 :07b}_{(ins >> 20) & 0b11111:05b}_{(ins >> 15) & 0b11111:03b}_{(ins >> 12) & 0b111:03b}_{(ins >> 7) & 0b11111:05b}_{ins & 0b1111111:07b}"); + f"0b{ins >> 25 :07b}_{(ins >> 20) & 0b11111:05b}_{(ins >> 15) & 0b11111:05b}_{(ins >> 12) & 0b111:03b}_{(ins >> 7) & 0b11111:05b}_{ins & 0b1111111:07b}"); STATIC_INSN: Dict[int, Tuple[str, List[int], int]] = { -- 2.40.1 From d9e5d78f878631556f0d0972377354aa504472ba Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 00:19:36 +0200 Subject: [PATCH 42/69] [Registers] removed info when writing to zero register --- riscemu/Registers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/riscemu/Registers.py b/riscemu/Registers.py index 2a58006..d3dfe47 100644 --- a/riscemu/Registers.py +++ b/riscemu/Registers.py @@ -95,7 +95,6 @@ class Registers: :return: If the operation was successful """ if reg == 'zero': - print("[Registers.set] trying to set read-only register: {}".format(reg)) return False if reg not in Registers.all_registers(): raise InvalidRegisterException(reg) -- 2.40.1 From 052ad56310db8ca834f0b2fcca8a97ec9ad63fa6 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 00:20:25 +0200 Subject: [PATCH 43/69] [CSR] fixed call to enum value member --- riscemu/priv/CSR.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index d9a7d00..1327315 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -84,7 +84,7 @@ class CSR: return inner def assert_can_read(self, mode: PrivModes, addr: int): - if (addr >> 8) & 3 > mode.value(): + if (addr >> 8) & 3 > mode.value: raise InstructionAccessFault(addr) def assert_can_write(self, mode: PrivModes, addr: int): -- 2.40.1 From 5b2b12507dbf14a9e2881ee5150a8c319009f77c Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 00:21:05 +0200 Subject: [PATCH 44/69] [PrivRV32I] added half od csrrs instruction (reading only) --- riscemu/priv/PrivRV32I.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index c30cb30..896b896 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -32,7 +32,15 @@ class PrivRV32I(RV32I): self.cpu.csr.set(csr_addr, new_val) def instruction_csrrs(self, ins: 'LoadedInstruction'): - INS_NOT_IMPLEMENTED(ins) + rd, rs, csr_addr = self.parse_crs_ins(ins) + if rs != 'zero': + # oh no, this should not happen! + INS_NOT_IMPLEMENTED(ins) + if rd != 'zero': + self.cpu.csr.assert_can_read(self.cpu.mode, csr_addr) + old_val = self.cpu.csr.get(csr_addr) + self.regs.set(rd, old_val) + def instruction_csrrc(self, ins: 'LoadedInstruction'): INS_NOT_IMPLEMENTED(ins) -- 2.40.1 From c2002cd46d7d3744343dace57bb0dafc51805a13 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 00:21:51 +0200 Subject: [PATCH 45/69] [PrivCPU] fixed naming for csr mtimecmp callback function --- .gitignore | 3 ++- riscemu/priv/PrivCPU.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index d75edea..9bccae6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ venv -__pycache__ \ No newline at end of file +__pycache__ +.mypy_cache \ No newline at end of file diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index ef34bf0..867ab38 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -134,7 +134,7 @@ class PrivCPU(CPU): self._time_interrupt_enabled = True @self.csr.callback('mtimecmph') - def mtimecmp(old, new): + def mtimecmph(old, new): self._time_timecmp = (new << 32) + self.csr.get('mtimecmp') self._time_interrupt_enabled = True -- 2.40.1 From 4c7f3ffe67697945aabb323ca6507e519c4f7860 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 00:22:30 +0200 Subject: [PATCH 46/69] [PrivCPU] fixed perf-counter not comparing against shifted time --- riscemu/priv/PrivCPU.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 867ab38..8368e0f 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -142,11 +142,11 @@ class PrivCPU(CPU): @self.csr.virtual_register('time') def get_time(): - return (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) & (2 ** 32 - 1) + return (time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start) & (2 ** 32 - 1) @self.csr.virtual_register('timeh') def get_timeh(): - return (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) >> 32 + return (time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start) >> 32 # add minstret and mcycle counters -- 2.40.1 From c25b9f23433d0494c55cd33ac7715b60398ba16f Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 00:23:09 +0200 Subject: [PATCH 47/69] [PrivCPU] implemented CPU interrupt handling context switch --- riscemu/priv/PrivCPU.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 8368e0f..8ca785b 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -173,6 +173,36 @@ class PrivCPU(CPU): if self._time_timecmp < (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start: self.pending_traps.append(TimerInterrupt()) self._time_interrupt_enabled = False + print(FMT_CPU + "[CPU] raising timer interrupt: tartegt: {}, current: {}".format(self._time_timecmp, (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start) + FMT_NONE) def _check_interrupt(self): - pass + if not (len(self.pending_traps) > 0 and self.csr.get_mstatus('mie')): + return + # select best interrupt + # TODO: actually select based on the official ranking + trap = self.pending_traps.pop() # use the most recent trap + print(FMT_CPU + "[CPU] taking trap {}!".format(trap) + FMT_NONE) + + if trap.priv != PrivModes.MACHINE: + print(FMT_CPU + "[CPU] Trap not targeting machine mode encountered! - undefined behaviour!" + FMT_NONE) + + if self.mode != PrivModes.USER: + print(FMT_CPU + "[CPU] Trap triggered outside of user mode?!" + FMT_NONE) + + self.csr.set_mstatus('mpie', self.csr.get_mstatus('mie')) + self.csr.set_mstatus('mpp', self.mode.value) + self.csr.set_mstatus('mie', 0) + self.csr.set('mcause', trap.mcause) + self.csr.set('mepc', self.pc) + self.csr.set('mtval', trap.mtval) + self.mode = trap.priv + mtvec = self.csr.get('mtvec') + if mtvec & 0b11 == 0: + self.pc = mtvec + if mtvec & 0b11 == 1: + self.pc = (mtvec & 0b11111111111111111111111111111100) + (trap.code * 4) + + + + + -- 2.40.1 From 639f91b19263e09b64bfdee4d42e54f96a2c6aaf Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 11:31:58 +0200 Subject: [PATCH 48/69] [decoder] removed sign extension for CSR type instructions --- riscemu/decoder/formats.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/riscemu/decoder/formats.py b/riscemu/decoder/formats.py index 296e6ca..9d3e544 100644 --- a/riscemu/decoder/formats.py +++ b/riscemu/decoder/formats.py @@ -98,6 +98,10 @@ def decode_i_shamt(ins: int) -> List[int]: return decode_i(ins) +def decode_i_unsigned(ins: int) -> List[int]: + return [rd(ins), rs1(ins), imm110(ins)] + + INSTRUCTION_ARGS_DECODER: Dict[int, Callable[[int], List[int]]] = { 0x00: decode_i, 0x04: decode_i_shamt, @@ -108,5 +112,5 @@ INSTRUCTION_ARGS_DECODER: Dict[int, Callable[[int], List[int]]] = { 0x18: decode_b, 0x19: decode_i, 0x1b: decode_j, - 0x1c: decode_i + 0x1c: decode_i_unsigned } -- 2.40.1 From 48ce44993b71452ad55a75cb00aa389df2fda311 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 11:32:27 +0200 Subject: [PATCH 49/69] [CSR] Adding dump_mstatus method to csr --- riscemu/colors.py | 1 + riscemu/priv/CSR.py | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/riscemu/colors.py b/riscemu/colors.py index 8b5ddbb..c2a7182 100644 --- a/riscemu/colors.py +++ b/riscemu/colors.py @@ -26,3 +26,4 @@ FMT_PARSE = FMT_CYAN + FMT_BOLD FMT_CPU = FMT_BLUE + FMT_BOLD FMT_SYSCALL = FMT_YELLOW + FMT_BOLD FMT_DEBUG = FMT_MAGENTA + FMT_BOLD +FMT_CSR = FMT_ORANGE + FMT_BOLD \ No newline at end of file diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index 1327315..ef67696 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -3,6 +3,8 @@ from collections import defaultdict from .privmodes import PrivModes from .Exceptions import InstructionAccessFault from ..helpers import to_unsigned +from ..colors import FMT_CSR, FMT_NONE + from .CSRConsts import CSR_NAME_TO_ADDR, MSTATUS_LEN_2, MSTATUS_OFFSETS class CSR: @@ -109,3 +111,13 @@ class CSR: return func return inner + + def dump_mstatus(self): + print(FMT_CSR + "[CSR] dumping mstatus:") + i = 0 + for name in MSTATUS_OFFSETS: + print(" {:<5} {}".format(name, self.get_mstatus(name)), end="") + if i % 6 == 5: + print() + i += 1 + print(FMT_NONE) -- 2.40.1 From affaa60d2297b14108924ebed0950f2350d6b0fc Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 11:34:28 +0200 Subject: [PATCH 50/69] [PrivCPU] adding performance counter --- riscemu/priv/PrivCPU.py | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 8ca785b..e0cd30c 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -61,6 +61,9 @@ class PrivCPU(CPU): self._time_timecmp = 0 self._time_interrupt_enabled = False + # performance counters + self._perf_counters = list() + # init csr self._init_csr() @@ -156,6 +159,8 @@ class PrivCPU(CPU): def step(self, verbose=True): try: + if self.cycle % 1000 == 0: + self._perf_counters.append((time.perf_counter_ns(), self.cycle)) self.cycle += 1 self._timer_step() self._check_interrupt() @@ -202,7 +207,25 @@ class PrivCPU(CPU): if mtvec & 0b11 == 1: self.pc = (mtvec & 0b11111111111111111111111111111100) + (trap.code * 4) - - - - + def show_perf(self): + timed = 0 + cycled = 0 + cps_list = list() + + print(FMT_CPU + "[CPU] Performance overview:") + for time_ns, cycle in self._perf_counters[-11:]: + if cycled == 0: + cycled = cycle + timed = time_ns + continue + cps = (cycle - cycled) / (time_ns - timed) * 1000000000 + + print(" {:03d} cycles in {:08d}ns ({:.2f} cycles/s)".format( + cycle - cycled, + time_ns - timed, + cps + )) + cycled = cycle + timed = time_ns + cps_list.append(cps) + print(" on average {:.0f} cycles/s".format(sum(cps_list) / len(cps_list)) + FMT_NONE) \ No newline at end of file -- 2.40.1 From cc598c09106273a3d477eb54be61ce20edd1c633 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 11:35:20 +0200 Subject: [PATCH 51/69] [PrivCPU] changed timer compare to lower equals to trigger exactly on time --- riscemu/priv/PrivCPU.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index e0cd30c..270f032 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -175,7 +175,7 @@ class PrivCPU(CPU): def _timer_step(self): if not self._time_interrupt_enabled: return - if self._time_timecmp < (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start: + if self._time_timecmp <= (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start: self.pending_traps.append(TimerInterrupt()) self._time_interrupt_enabled = False print(FMT_CPU + "[CPU] raising timer interrupt: tartegt: {}, current: {}".format(self._time_timecmp, (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start) + FMT_NONE) -- 2.40.1 From c7b36937402545d95e1f42aab450773240d04651 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 11:36:00 +0200 Subject: [PATCH 52/69] [Regsietrs] ensuring register values are 32bit --- riscemu/Registers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/riscemu/Registers.py b/riscemu/Registers.py index d3dfe47..ab74821 100644 --- a/riscemu/Registers.py +++ b/riscemu/Registers.py @@ -103,7 +103,7 @@ class Registers: reg = 's1' if mark_set: self.last_set = reg - self.vals[reg] = val + self.vals[reg] = val & (2**32 - 1) return True def get(self, reg, mark_read=True): -- 2.40.1 From 777717ed2e21c3bfaf18ae7a89bb1e61c55d354f Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 11:36:33 +0200 Subject: [PATCH 53/69] [PrivRV32I] fixed csrrw instruction to correctly switch register contents --- riscemu/priv/PrivRV32I.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index 896b896..78631be 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -22,14 +22,16 @@ class PrivRV32I(RV32I): def instruction_csrrw(self, ins: 'LoadedInstruction'): rd, rs, csr_addr = self.parse_crs_ins(ins) + old_val = None if rd != 'zero': self.cpu.csr.assert_can_read(self.cpu.mode, csr_addr) old_val = self.cpu.csr.get(csr_addr) - self.regs.set(rd, old_val) if rs != 'zero': new_val = self.regs.get(rs) self.cpu.csr.assert_can_write(self.cpu.mode, csr_addr) self.cpu.csr.set(csr_addr, new_val) + if old_val is not None: + self.regs.set(rd, old_val) def instruction_csrrs(self, ins: 'LoadedInstruction'): rd, rs, csr_addr = self.parse_crs_ins(ins) -- 2.40.1 From baa1f24eb75bca9fc22ac56db0c05a7efeb6a31c Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 14:42:44 +0200 Subject: [PATCH 54/69] [CpuTraps] fixed formatting for mcause registers --- riscemu/priv/Exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/riscemu/priv/Exceptions.py b/riscemu/priv/Exceptions.py index db3a544..2dcbbfd 100644 --- a/riscemu/priv/Exceptions.py +++ b/riscemu/priv/Exceptions.py @@ -50,7 +50,7 @@ class CpuTrap(BaseException): @property def mcause(self): - return (self.code << 31) + self.interrupt + return (self.interrupt << 31) + self.code def __repr__(self): name = "Reserved interrupt({}, {})".format(self.interrupt, self.code) -- 2.40.1 From 05c17bc029d41beaaddb8d2d2186aa1cdfbf1d0e Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 14:44:13 +0200 Subject: [PATCH 55/69] [PrivCPU] fixed debugger skipping over ebreak instructions --- riscemu/priv/PrivCPU.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 270f032..6d0e53a 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -91,7 +91,8 @@ class PrivCPU(CPU): self.launch_debug = False launch_debug_session(self, self.mmu, self.regs, "Launching debugger:") - self._run(verbose) + if not self.active_debug: + self._run(verbose) else: print() print(FMT_CPU + "Program stopped without exiting - perhaps you stopped the debugger?" + FMT_NONE) -- 2.40.1 From 6b4f38d0303055f6593c558e3592f63b1d87918a Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 15:07:51 +0200 Subject: [PATCH 56/69] [ElfLoader] added cache for already decoded instructions --- riscemu/priv/ElfLoader.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/riscemu/priv/ElfLoader.py b/riscemu/priv/ElfLoader.py index f908f4d..e6084fa 100644 --- a/riscemu/priv/ElfLoader.py +++ b/riscemu/priv/ElfLoader.py @@ -124,13 +124,26 @@ class ElfInstruction: class ElfLoadedMemorySection(LoadedMemorySection): + ins_cache: List[Optional[ElfInstruction]] + """ + A fast cache for accessing pre-decoded instructions + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.__setattr__('ins_cache', [None] * (self.size // 4)) + def read_instruction(self, offset): + if self.ins_cache[offset//4] is not None: + return self.ins_cache[offset//4] if not self.flags.executable: print(FMT_PARSE + "Reading instruction from non-executable memory!" + FMT_NONE) raise InstructionAccessFault(offset + self.base) if offset % 4 != 0: raise InstructionAddressMisalignedTrap(offset + self.base) - return ElfInstruction(*decode(self.content[offset:offset + 4])) + ins = ElfInstruction(*decode(self.content[offset:offset + 4])) + self.ins_cache[offset // 4] = ins + return ins @property def end(self): -- 2.40.1 From 60a2a8d54698abd639ae1b60cc58e9127025ccee Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 16:32:39 +0200 Subject: [PATCH 57/69] [CSR] adding cache to mstatus register --- riscemu/priv/CSR.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index ef67696..547f883 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -7,6 +7,7 @@ from ..colors import FMT_CSR, FMT_NONE from .CSRConsts import CSR_NAME_TO_ADDR, MSTATUS_LEN_2, MSTATUS_OFFSETS + class CSR: """ This holds all Control and Status Registers (CSR) @@ -21,14 +22,17 @@ class CSR: list of virtual CSR registers, with values computed on read """ - listeners: Dict[int, Callable[[int, int], None]] + mstatus_cache: Dict[str, int] + mstatus_cache_dirty = True + def __init__(self): self.regs = defaultdict(lambda: 0) self.listeners = defaultdict(lambda: (lambda x, y: None)) self.virtual_regs = dict() - #TODO: implement write masks (bitmasks which control writeable bits in registers + self.mstatus_cache = dict() + # TODO: implement write masks (bitmasks which control writeable bits in registers def set(self, addr: Union[str, int], val: int): addr = self._name_to_addr(addr) @@ -36,6 +40,8 @@ class CSR: return val = to_unsigned(val) self.listeners[addr](self.regs[addr], val) + if addr == 0x300: + self.mstatus_cache_dirty = True self.regs[addr] = val def get(self, addr: Union[str, int]) -> int: @@ -67,22 +73,31 @@ class CSR: """ size = 2 if name in MSTATUS_LEN_2 else 1 off = MSTATUS_OFFSETS[name] - mask = (2**size - 1) << off + mask = (2 ** size - 1) << off old_val = self.get('mstatus') erased = old_val & (~mask) new_val = erased | (val << off) self.set('mstatus', new_val) def get_mstatus(self, name) -> int: + if not self.mstatus_cache_dirty and name in self.mstatus_cache: + return self.mstatus_cache[name] + size = 2 if name in MSTATUS_LEN_2 else 1 off = MSTATUS_OFFSETS[name] - mask = (2**size - 1) << off - return (self.get('mstatus') & mask) >> off + mask = (2 ** size - 1) << off + val = (self.get('mstatus') & mask) >> off + if self.mstatus_cache_dirty: + self.mstatus_cache = dict(name=val) + else: + self.mstatus_cache[name] = val + return val def callback(self, addr: Union[str, int]): def inner(func: Callable[[int, int], None]): self.set_listener(addr, func) return func + return inner def assert_can_read(self, mode: PrivModes, addr: int): -- 2.40.1 From 3d07c97a5283160ac71876b717e5489f86599e01 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 16:33:48 +0200 Subject: [PATCH 58/69] [PrivCPU] improved step function performance by checking time every tenth cycle --- riscemu/priv/PrivCPU.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 6d0e53a..12089d6 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -160,10 +160,9 @@ class PrivCPU(CPU): def step(self, verbose=True): try: - if self.cycle % 1000 == 0: - self._perf_counters.append((time.perf_counter_ns(), self.cycle)) self.cycle += 1 - self._timer_step() + if self.cycle % 10 == 0: + self._timer_step() self._check_interrupt() ins = self.mmu.read_ins(self.pc) if verbose: @@ -207,6 +206,9 @@ class PrivCPU(CPU): self.pc = mtvec if mtvec & 0b11 == 1: self.pc = (mtvec & 0b11111111111111111111111111111100) + (trap.code * 4) + self.record_perf_profile() + if len(self._perf_counters) % 100 == 0: + self.show_perf() def show_perf(self): timed = 0 @@ -214,19 +216,23 @@ class PrivCPU(CPU): cps_list = list() print(FMT_CPU + "[CPU] Performance overview:") - for time_ns, cycle in self._perf_counters[-11:]: + for time_ns, cycle in self._perf_counters: if cycled == 0: cycled = cycle timed = time_ns continue cps = (cycle - cycled) / (time_ns - timed) * 1000000000 - print(" {:03d} cycles in {:08d}ns ({:.2f} cycles/s)".format( - cycle - cycled, - time_ns - timed, - cps - )) + #print(" {:03d} cycles in {:08d}ns ({:.2f} cycles/s)".format( + # cycle - cycled, + # time_ns - timed, + # cps + #)) cycled = cycle timed = time_ns cps_list.append(cps) - print(" on average {:.0f} cycles/s".format(sum(cps_list) / len(cps_list)) + FMT_NONE) \ No newline at end of file + print(" on average {:.0f} cycles/s".format(sum(cps_list) / len(cps_list)) + FMT_NONE) + self._perf_counters = list() + + def record_perf_profile(self): + self._perf_counters.append((time.perf_counter_ns(), self.cycle)) \ No newline at end of file -- 2.40.1 From e8685af328cdc302eb009ccad2c2d8bfdd946e16 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 16:34:19 +0200 Subject: [PATCH 59/69] [PrivMMU] cleaned up file formatting --- riscemu/priv/PrivMMU.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/riscemu/priv/PrivMMU.py b/riscemu/priv/PrivMMU.py index 2d67af8..0e8bb40 100644 --- a/riscemu/priv/PrivMMU.py +++ b/riscemu/priv/PrivMMU.py @@ -4,6 +4,7 @@ import typing from .ElfLoader import ElfExecutable + class PrivMMU(MMU): def __init__(self, elf: ElfExecutable): super(PrivMMU, self).__init__(conf=RunConfig()) @@ -17,9 +18,3 @@ class PrivMMU(MMU): def allocate_section(self, name: str, req_size: int, flag: MemoryFlags): raise NotImplementedError("Not supported!") - - - - - - -- 2.40.1 From 4c352d85679877f0249bfe2ff0cf3a2767a68a8a Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 8 Jun 2021 16:34:45 +0200 Subject: [PATCH 60/69] [MMU] caching last used code section --- riscemu/MMU.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/riscemu/MMU.py b/riscemu/MMU.py index 5777f67..0d9c8ff 100644 --- a/riscemu/MMU.py +++ b/riscemu/MMU.py @@ -47,6 +47,8 @@ class MMU: The global symbol table """ + last_ins_sec: Optional[LoadedMemorySection] + def __init__(self, conf: RunConfig): """ Create a new MMU, respecting the active RunConfiguration @@ -58,6 +60,7 @@ class MMU: self.first_free_addr: int = 0x100 self.conf: RunConfig = conf self.global_symbols: Dict[str, int] = dict() + self.last_ins_sec = None def load_bin(self, exe: Executable) -> LoadedExecutable: """ @@ -140,7 +143,11 @@ class MMU: :param addr: The location :return: The Instruction """ + sec = self.last_ins_sec + if sec is not None and sec.base <= addr < sec.base + sec.size: + return sec.read_instruction(addr - sec.base) sec = self.get_sec_containing(addr) + self.last_ins_sec = sec 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) -- 2.40.1 From 1f03449694e5eb0101ddf7aa9d8f16b335268836 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Thu, 26 Aug 2021 10:46:06 +0200 Subject: [PATCH 61/69] added memory image support to priv emulator --- riscemu/CPU.py | 3 +-- riscemu/Registers.py | 15 ++++++++++----- riscemu/debug.py | 4 +--- riscemu/decoder/decoder.py | 7 ++----- riscemu/helpers.py | 11 +++-------- riscemu/priv/CSR.py | 4 ++-- riscemu/priv/ElfLoader.py | 2 +- riscemu/priv/Exceptions.py | 7 ++++++- riscemu/priv/PrivCPU.py | 33 +++++++++++++++++++++------------ riscemu/priv/PrivRV32I.py | 7 +------ riscemu/priv/__main__.py | 9 ++++++++- 11 files changed, 56 insertions(+), 46 deletions(-) diff --git a/riscemu/CPU.py b/riscemu/CPU.py index 469092f..962ef85 100644 --- a/riscemu/CPU.py +++ b/riscemu/CPU.py @@ -148,8 +148,7 @@ class CPU: 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:") + launch_debug_session(self, self.mmu, self.regs, "Exception encountered, launching debug:") if self.exit: print() diff --git a/riscemu/Registers.py b/riscemu/Registers.py index ab74821..3cc10db 100644 --- a/riscemu/Registers.py +++ b/riscemu/Registers.py @@ -96,14 +96,19 @@ class Registers: """ if reg == 'zero': return False - if reg not in Registers.all_registers(): - raise InvalidRegisterException(reg) + #if reg not in Registers.all_registers(): + # raise InvalidRegisterException(reg) # replace fp register with s1, as these are the same register if reg == 'fp': reg = 's1' if mark_set: self.last_set = reg - self.vals[reg] = val & (2**32 - 1) + # check 32 bit signed bounds + if val < -2147483648: + val = -2147483648 + elif val > 2147483647: + val = 2147483647 + self.vals[reg] = val return True def get(self, reg, mark_read=True): @@ -113,8 +118,8 @@ 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(): - raise InvalidRegisterException(reg) + #if reg not in Registers.all_registers(): + # raise InvalidRegisterException(reg) if reg == 'fp': reg = 's0' if mark_read: diff --git a/riscemu/debug.py b/riscemu/debug.py index 9e6704d..d8a656d 100644 --- a/riscemu/debug.py +++ b/riscemu/debug.py @@ -45,9 +45,7 @@ def launch_debug_session(cpu: 'CPU', mmu: 'MMU', reg: 'Registers', prompt=""): print("Invalid arg count!") return bin = mmu.get_bin_containing(cpu.pc) - if bin is None: - print(FMT_DEBUG + '[Debugger] Not in a section, can\'t execute instructions!' + FMT_NONE) - return + ins = LoadedInstruction(name, list(args), bin) print(FMT_DEBUG + "Running instruction " + ins + FMT_NONE) cpu.run_instruction(ins) diff --git a/riscemu/decoder/decoder.py b/riscemu/decoder/decoder.py index 462add4..267fdf6 100644 --- a/riscemu/decoder/decoder.py +++ b/riscemu/decoder/decoder.py @@ -23,10 +23,7 @@ STATIC_INSN: Dict[int, Tuple[str, List[int], int]] = { def int_from_ins(insn: bytearray): - return (insn[3] << (8 * 3)) + \ - (insn[2] << (8 * 2)) + \ - (insn[1] << 8) + \ - insn[0] + return int.from_bytes(insn, 'little') def name_from_insn(ins: int): @@ -68,7 +65,7 @@ def name_from_insn(ins: int): raise RuntimeError(f"Invalid instruction: {ins:x}") -def decode(ins: bytearray) -> Tuple[str, List[int], int]: +def decode(ins: Union[bytearray, bytes]) -> Tuple[str, List[int], int]: insn = int_from_ins(ins) if insn & 3 != 3: diff --git a/riscemu/helpers.py b/riscemu/helpers.py index 97458cc..85b64af 100644 --- a/riscemu/helpers.py +++ b/riscemu/helpers.py @@ -33,19 +33,14 @@ def int_to_bytes(val, bytes=4, unsigned=False) -> bytearray: """ if unsigned and val < 0: raise NumberFormatException("unsigned negative number!") - return bytearray([ - (val >> ((bytes - i - 1) * 8)) & 0xFF for i in range(bytes) - ]) + return bytearray(to_unsigned(val, bytes).to_bytes(bytes, 'little')) def int_from_bytes(bytes, unsigned=False) -> int: """ byte -> int (two's complement) """ - num = 0 - for b in bytes: - num = num << 8 - num += b + num = int.from_bytes(bytes, 'little') if unsigned: return num @@ -55,7 +50,7 @@ def int_from_bytes(bytes, unsigned=False) -> int: def to_unsigned(num: int, bytes=4) -> int: if num < 0: - return 2 ** (bytes * 8) + num + return (2 ** (bytes * 8)) + num return num diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index 547f883..4a2cc7b 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -2,7 +2,7 @@ from typing import Dict, Union, Callable, Optional from collections import defaultdict from .privmodes import PrivModes from .Exceptions import InstructionAccessFault -from ..helpers import to_unsigned +from ..helpers import to_signed from ..colors import FMT_CSR, FMT_NONE from .CSRConsts import CSR_NAME_TO_ADDR, MSTATUS_LEN_2, MSTATUS_OFFSETS @@ -38,7 +38,7 @@ class CSR: addr = self._name_to_addr(addr) if addr is None: return - val = to_unsigned(val) + val = to_signed(val) self.listeners[addr](self.regs[addr], val) if addr == 0x300: self.mstatus_cache_dirty = True diff --git a/riscemu/priv/ElfLoader.py b/riscemu/priv/ElfLoader.py index e6084fa..8f71740 100644 --- a/riscemu/priv/ElfLoader.py +++ b/riscemu/priv/ElfLoader.py @@ -39,7 +39,7 @@ class ElfExecutable: if not elf.header.e_ident.EI_CLASS == 'ELFCLASS32': raise InvalidElfException("Only 32bit executables are supported!") - self.run_ptr = elf.header.e_entry; + self.run_ptr = elf.header.e_entry for sec in elf.iter_sections(): if isinstance(sec, SymbolTableSection): diff --git a/riscemu/priv/Exceptions.py b/riscemu/priv/Exceptions.py index 2dcbbfd..85503c3 100644 --- a/riscemu/priv/Exceptions.py +++ b/riscemu/priv/Exceptions.py @@ -58,7 +58,7 @@ class CpuTrap(BaseException): if (self.interrupt, self.code) in MCAUSE_TRANSLATION: name = MCAUSE_TRANSLATION[(self.interrupt, self.code)] + "({}, {})".format(self.interrupt, self.code) - return "{} {{priv={}, type={}, mtval={}}}".format( + return "{} {{priv={}, type={}, mtval={:x}}}".format( name, self.priv.name, self.type.name, self.mtval ) @@ -84,3 +84,8 @@ class InstructionAccessFault(CpuTrap): class TimerInterrupt(CpuTrap): def __init__(self): super().__init__(7, 0, CpuTrapType.TIMER) + + +class EcallTrap(CpuTrap): + def __init__(self, mode: PrivModes): + super().__init__(mode.value + 8, 0, CpuTrapType.SOFTWARE) diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 12089d6..aec1cd6 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -8,11 +8,14 @@ import time from riscemu.CPU import * from .CSR import CSR from .ElfLoader import ElfExecutable +from .ImageLoader import ContinuousMMU from .Exceptions import * from .PrivMMU import PrivMMU +from ..IO import TextIO from .PrivRV32I import PrivRV32I from .privmodes import PrivModes from ..instructions.RV32M import RV32M +import json if typing.TYPE_CHECKING: from riscemu import Executable, LoadedExecutable, LoadedInstruction @@ -48,9 +51,14 @@ class PrivCPU(CPU): super().__init__(conf, [PrivRV32I, RV32M]) self.mode: PrivModes = PrivModes.MACHINE - kernel = ElfExecutable('kernel') - self.mmu = PrivMMU(kernel) - self.pc = kernel.run_ptr + with open('mem.img', 'rb') as memf: + data = memf.read() + with open('mem.img.dbg', 'r') as dbgf: + debug_info = json.load(dbgf) + + self.mmu = ContinuousMMU(data, debug_info, self) + self.pc = 0x100 + self.mmu.add_io(TextIO.TextIO(0xff0000, 64)) self.syscall_int = None self.launch_debug = False @@ -109,7 +117,7 @@ class PrivCPU(CPU): def run(self): print(FMT_CPU + '[CPU] Started running from 0x{:08X} ({})'.format(self.pc, "kernel") + FMT_NONE) self._time_start = time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._run(True) + self._run() def _init_csr(self): # set up CSR @@ -161,7 +169,7 @@ class PrivCPU(CPU): def step(self, verbose=True): try: self.cycle += 1 - if self.cycle % 10 == 0: + if self.cycle % 20 == 0: self._timer_step() self._check_interrupt() ins = self.mmu.read_ins(self.pc) @@ -178,7 +186,6 @@ class PrivCPU(CPU): if self._time_timecmp <= (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start: self.pending_traps.append(TimerInterrupt()) self._time_interrupt_enabled = False - print(FMT_CPU + "[CPU] raising timer interrupt: tartegt: {}, current: {}".format(self._time_timecmp, (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start) + FMT_NONE) def _check_interrupt(self): if not (len(self.pending_traps) > 0 and self.csr.get_mstatus('mie')): @@ -186,10 +193,12 @@ class PrivCPU(CPU): # select best interrupt # TODO: actually select based on the official ranking trap = self.pending_traps.pop() # use the most recent trap - print(FMT_CPU + "[CPU] taking trap {}!".format(trap) + FMT_NONE) + if not isinstance(trap, TimerInterrupt): + print(FMT_CPU + "[CPU] taking trap {}!".format(trap) + FMT_NONE) if trap.priv != PrivModes.MACHINE: print(FMT_CPU + "[CPU] Trap not targeting machine mode encountered! - undefined behaviour!" + FMT_NONE) + raise Exception("Undefined behaviour!") if self.mode != PrivModes.USER: print(FMT_CPU + "[CPU] Trap triggered outside of user mode?!" + FMT_NONE) @@ -198,7 +207,7 @@ class PrivCPU(CPU): self.csr.set_mstatus('mpp', self.mode.value) self.csr.set_mstatus('mie', 0) self.csr.set('mcause', trap.mcause) - self.csr.set('mepc', self.pc) + self.csr.set('mepc', self.pc-self.INS_XLEN) self.csr.set('mtval', trap.mtval) self.mode = trap.priv mtvec = self.csr.get('mtvec') @@ -223,16 +232,16 @@ class PrivCPU(CPU): continue cps = (cycle - cycled) / (time_ns - timed) * 1000000000 - #print(" {:03d} cycles in {:08d}ns ({:.2f} cycles/s)".format( + # print(" {:03d} cycles in {:08d}ns ({:.2f} cycles/s)".format( # cycle - cycled, # time_ns - timed, # cps - #)) + # )) cycled = cycle timed = time_ns cps_list.append(cps) - print(" on average {:.0f} cycles/s".format(sum(cps_list) / len(cps_list)) + FMT_NONE) + print(" on average {:.0f} instructions/s".format(sum(cps_list) / len(cps_list)) + FMT_NONE) self._perf_counters = list() def record_perf_profile(self): - self._perf_counters.append((time.perf_counter_ns(), self.cycle)) \ No newline at end of file + self._perf_counters.append((time.perf_counter_ns(), self.cycle)) diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index 78631be..c61c344 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -88,12 +88,7 @@ class PrivRV32I(RV32I): """ Overwrite the scall from userspace RV32I """ - if self.cpu.mode == PrivModes.USER: - raise CpuTrap(8, 0, CpuTrapType.SOFTWARE, self.cpu.mode) # ecall from U mode - elif self.cpu.mode == PrivModes.SUPER: - raise CpuTrap(9, 0, CpuTrapType.SOFTWARE, self.cpu.mode) # ecall from S mode - should not happen - elif self.cpu.mode == PrivModes.MACHINE: - raise CpuTrap(11, 0, CpuTrapType.SOFTWARE, self.cpu.mode) # ecall from M mode + raise EcallTrap(self.cpu.mode) def instruction_beq(self, ins: 'LoadedInstruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins) diff --git a/riscemu/priv/__main__.py b/riscemu/priv/__main__.py index 0cd2184..47820b7 100644 --- a/riscemu/priv/__main__.py +++ b/riscemu/priv/__main__.py @@ -6,11 +6,18 @@ from ..ExecutableParser import ExecutableParser import sys if __name__ == '__main__': + import argparse - files = sys.argv + #parser = argparse.ArgumentParser(description='RISC-V privileged architecture emulator', prog='riscemu') + + #parser.add_argument('--kernel', type=str, help='Kernel elf loaded with user programs', nargs='?') + #parser.add_argument('--image', type=str, help='Memory image containing kernel', nargs='?') + + #args = parser.parse_args() cpu = PrivCPU(RunConfig()) cpu.run() + -- 2.40.1 From df9e610d14d26d016a2ac0413b7930699332d3b2 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Thu, 26 Aug 2021 10:47:47 +0200 Subject: [PATCH 62/69] forgot to commit image loader code --- riscemu/priv/ImageLoader.py | 104 ++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 riscemu/priv/ImageLoader.py diff --git a/riscemu/priv/ImageLoader.py b/riscemu/priv/ImageLoader.py new file mode 100644 index 0000000..b51bb3e --- /dev/null +++ b/riscemu/priv/ImageLoader.py @@ -0,0 +1,104 @@ +""" +Laods a memory image with debug information into memory +""" + +from ..MMU import MMU +from ..Config import RunConfig +from ..Executable import Executable, LoadedExecutable, LoadedMemorySection, LoadedInstruction, MemoryFlags +from .ElfLoader import ElfInstruction, ElfLoadedMemorySection, InstructionAccessFault, InstructionAddressMisalignedTrap +from ..decoder import decode +from ..IO.IOModule import IOModule +from .privmodes import PrivModes +from ..colors import FMT_ERROR, FMT_NONE + +from functools import lru_cache +from typing import Dict, List, Tuple, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from .PrivCPU import PrivCPU + + +class ContinuousMMU(MMU): + io: List[IOModule] + data: bytearray + io_start: int + debug_info: Dict[str, Dict[str, str]] + + def __init__(self, data: bytes, debug_info: Dict, cpu, io_start: int = 0xFF0000): + super(ContinuousMMU, self).__init__(conf=RunConfig()) + self.cpu: 'PrivCPU' = cpu + self.data = bytearray(data) + if len(data) < io_start: + self.data += bytearray(io_start - len(data)) + self.debug_info = debug_info + self.io_start = io_start + self.io = list() + self.kernel_end = 0 + for start, name in debug_info['sections'].items(): + if name.startswith('programs'): + self.kernel_end = int(start) + break + + @lru_cache(maxsize=2048) + def read_ins(self, addr: int) -> ElfInstruction: + if addr >= self.io_start: + raise InstructionAccessFault(addr) + if addr % 4 != 0: + raise InstructionAddressMisalignedTrap(addr) + + return ElfInstruction(*decode(self.data[addr:addr + 4])) + + def read(self, addr: int, size: int) -> bytearray: + if addr < 0x100: + pc = self.cpu.pc + text_sec = self.get_sec_containing(pc) + print(FMT_ERROR + "[MMU] possible null dereference (read {:x}) from (pc={:x},sec={},rel={:x})".format( + addr, pc, text_sec.owner + ':' + text_sec.name, pc - text_sec.base + ) + FMT_NONE) + if addr >= self.io_start: + return self.io_at(addr).read(addr, size) + return self.data[addr: addr + size] + + def write(self, addr: int, size: int, data): + if addr < 0x100: + pc = self.cpu.pc + text_sec = self.get_sec_containing(pc) + print(FMT_ERROR + "[MMU] possible null dereference (write {:x}) from (pc={:x},sec={},rel={:x})".format( + addr, pc, text_sec.owner + ':' + text_sec.name, pc - text_sec.base + ) + FMT_NONE) + if addr < self.kernel_end: + if self.cpu.mode != PrivModes.MACHINE: + pc = self.cpu.pc + text_sec = self.get_sec_containing(pc) + print(FMT_ERROR + "[MMU] kernel access to {:x} from outside kernel mode! (pc={:x},sec={},rel={:x})".format( + addr, pc, text_sec.owner + ':' + text_sec.name, pc - text_sec.base + ) + FMT_NONE) + + if addr >= self.io_start: + return self.io_at(addr).write(addr, data, size) + self.data[addr:addr + size] = data[0:size] + + def io_at(self, addr) -> IOModule: + for mod in self.io: + if mod.contains(addr): + return mod + raise InstructionAccessFault(addr) + + def add_io(self, io: IOModule): + self.io.append(io) + + def __repr__(self): + return "ImageMMU()" + + @lru_cache(maxsize=32) + def get_sec_containing(self, addr: int) -> Optional[LoadedMemorySection]: + next_sec = len(self.data) + for sec_addr, name in sorted(self.debug_info['sections'].items(), key=lambda x: int(x[0]), reverse=True): + if addr >= int(sec_addr): + owner, name = name.split(':') + base = int(sec_addr) + size = next_sec - base + flags = MemoryFlags('.text' in name, '.text' in name) + return ElfLoadedMemorySection(name, base, size, self.data[base:next_sec], flags, owner) + else: + next_sec = int(sec_addr) -- 2.40.1 From 684c8583003359e3250fb416799b4fb0f20d4647 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Thu, 26 Aug 2021 10:48:26 +0200 Subject: [PATCH 63/69] added support for IO modules --- riscemu/IO/IOModule.py | 22 ++++++++++ riscemu/IO/TextIO.py | 94 ++++++++++++++++++++++++++++++++++++++++++ riscemu/IO/__init__.py | 0 3 files changed, 116 insertions(+) create mode 100644 riscemu/IO/IOModule.py create mode 100644 riscemu/IO/TextIO.py create mode 100644 riscemu/IO/__init__.py diff --git a/riscemu/IO/IOModule.py b/riscemu/IO/IOModule.py new file mode 100644 index 0000000..21d6a97 --- /dev/null +++ b/riscemu/IO/IOModule.py @@ -0,0 +1,22 @@ +from abc import ABC, abstractmethod + + +class IOModule(ABC): + addr: int + size: int + + def __init__(self, addr: int, size: int): + self.addr = addr + self.size = size + + @abstractmethod + def read(self, addr: int, size: int): + pass + + @abstractmethod + def write(self, addr: int, data: bytearray, size: int): + pass + + def contains(self, addr, size: int = 0): + return self.addr <= addr < self.addr + self.size and \ + self.addr <= addr + size <= self.addr + self.size diff --git a/riscemu/IO/TextIO.py b/riscemu/IO/TextIO.py new file mode 100644 index 0000000..14312cc --- /dev/null +++ b/riscemu/IO/TextIO.py @@ -0,0 +1,94 @@ +from .IOModule import IOModule +from ..priv.Exceptions import InstructionAccessFault +from ..helpers import int_from_bytes +from threading import Thread +import time + + +def _window_loop(textIO: 'TextIO'): + #textIO.set_sg_window(None) + #return + try: + import PySimpleGUI as sg + + logs = sg.Text(font="monospace") + col = sg.Column([[logs]], size=(1920, 1080), scrollable=True) + window = sg.Window("TextIO:{:x}".format(textIO.addr), [[col]]) + lines = list() + + window.finalize() + textIO.set_sg_window(window) + while True: + e, v = window.read() + if e == sg.WINDOW_CLOSED: + window.close() + textIO.set_sg_window(None) + break + if e == 'putlog': + lines.insert(0, v[0]) + logs.update(value='\n'.join(lines) + '\n') + col.contents_changed() + + except ImportError: + print("[TextIO] module works best with PySimpleGui!") + + +class TextIO(IOModule): + def __init__(self, addr: int, buflen: int = 128): + super(TextIO, self).__init__(addr, buflen + 4) + self.buff = bytearray(buflen) + self.current_line = "" + self.sg_window = None + self.start_buffer = list() + + self.thread = Thread(target=_window_loop, args=(self,)) + self.thread.start() + time.sleep(0.1) + + def set_sg_window(self, window): + if self.sg_window is not None and window is not None: + raise Exception("cannot set window twice!") + self.sg_window = window + + buff = self.start_buffer + self.start_buffer = None if window is None else list() + + for line in buff: + self._present(line) + + def read(self, addr: int, size: int) -> bytearray: + raise InstructionAccessFault(addr) + + def write(self, addr: int, data: bytearray, size: int): + if addr == self.addr: + if size > 4: + raise InstructionAccessFault(addr) + if int_from_bytes(data[0:4]) > 0: + self._print() + return + buff_start = addr - self.addr - 4 + self.buff[buff_start:buff_start + size] = data[0:size] + + def _print(self): + buff = self.buff + self.buff = bytearray(self.size) + if b'\x00' in buff: + buff = buff.split(b'\x00')[0] + text = buff.decode('ascii') + if '\n' in text: + lines = text.split("\n") + lines[0] = self.current_line + lines[0] + for line in lines[:-1]: + self._present(line) + self.current_line = lines[-1] + else: + self.current_line += text + + def _present(self, text: str): + if self.sg_window is not None: + self.sg_window.write_event_value('putlog', text) + elif self.start_buffer is not None: + self.start_buffer.append(text) + else: + print("[TextIO:{:x}] {}".format(self.addr, text)) + diff --git a/riscemu/IO/__init__.py b/riscemu/IO/__init__.py new file mode 100644 index 0000000..e69de29 -- 2.40.1 From 0651eabe18ae5f97114f4dfbc94a0a42d3b893e6 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Mon, 30 Aug 2021 15:09:31 +0200 Subject: [PATCH 64/69] fixed how ecalls are represented and handled --- .gitignore | 2 +- riscemu/IO/TextIO.py | 2 +- riscemu/priv/Exceptions.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 9bccae6..57e5c62 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ venv __pycache__ -.mypy_cache \ No newline at end of file +.mypy_cache diff --git a/riscemu/IO/TextIO.py b/riscemu/IO/TextIO.py index 14312cc..b51813b 100644 --- a/riscemu/IO/TextIO.py +++ b/riscemu/IO/TextIO.py @@ -12,7 +12,7 @@ def _window_loop(textIO: 'TextIO'): import PySimpleGUI as sg logs = sg.Text(font="monospace") - col = sg.Column([[logs]], size=(1920, 1080), scrollable=True) + col = sg.Column([[logs]], size=(640, 400), scrollable=True) window = sg.Window("TextIO:{:x}".format(textIO.addr), [[col]]) lines = list() diff --git a/riscemu/priv/Exceptions.py b/riscemu/priv/Exceptions.py index 85503c3..fee6217 100644 --- a/riscemu/priv/Exceptions.py +++ b/riscemu/priv/Exceptions.py @@ -88,4 +88,4 @@ class TimerInterrupt(CpuTrap): class EcallTrap(CpuTrap): def __init__(self, mode: PrivModes): - super().__init__(mode.value + 8, 0, CpuTrapType.SOFTWARE) + super().__init__(mode.value + 8, 0, CpuTrapType.EXCEPTION) -- 2.40.1 From f2d07f90b592b5cf1058fc4091148aad048108c4 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Mon, 30 Aug 2021 15:40:13 +0200 Subject: [PATCH 65/69] priv: added __main__ script to module which correctly configures the cpu depending on --kernel or --image options --- riscemu/priv/ImageLoader.py | 43 +++++++++++++++++++++---------------- riscemu/priv/PrivCPU.py | 21 ++++++++---------- riscemu/priv/PrivMMU.py | 21 +++++++++++++++++- riscemu/priv/__main__.py | 22 ++++++++++++++----- 4 files changed, 70 insertions(+), 37 deletions(-) diff --git a/riscemu/priv/ImageLoader.py b/riscemu/priv/ImageLoader.py index b51bb3e..50cc25e 100644 --- a/riscemu/priv/ImageLoader.py +++ b/riscemu/priv/ImageLoader.py @@ -2,7 +2,7 @@ Laods a memory image with debug information into memory """ -from ..MMU import MMU +from .PrivMMU import PrivMMU from ..Config import RunConfig from ..Executable import Executable, LoadedExecutable, LoadedMemorySection, LoadedInstruction, MemoryFlags from .ElfLoader import ElfInstruction, ElfLoadedMemorySection, InstructionAccessFault, InstructionAddressMisalignedTrap @@ -10,6 +10,7 @@ from ..decoder import decode from ..IO.IOModule import IOModule from .privmodes import PrivModes from ..colors import FMT_ERROR, FMT_NONE +import json from functools import lru_cache from typing import Dict, List, Tuple, Optional, TYPE_CHECKING @@ -18,26 +19,37 @@ if TYPE_CHECKING: from .PrivCPU import PrivCPU -class ContinuousMMU(MMU): +class MemoryImageMMU(PrivMMU): io: List[IOModule] data: bytearray io_start: int - debug_info: Dict[str, Dict[str, str]] + debug_info: Dict[str, Dict[str, Dict[str, str]]] + + def __init__(self, file_name: str, io_start: int = 0xFF0000): + super(MemoryImageMMU, self).__init__(conf=RunConfig()) + + with open(file_name, 'rb') as memf: + data = memf.read() + with open(file_name + '.dbg', 'r') as dbgf: + debug_info: Dict = json.load(dbgf) - def __init__(self, data: bytes, debug_info: Dict, cpu, io_start: int = 0xFF0000): - super(ContinuousMMU, self).__init__(conf=RunConfig()) - self.cpu: 'PrivCPU' = cpu self.data = bytearray(data) + # TODO: super wasteful memory allocation happening here if len(data) < io_start: self.data += bytearray(io_start - len(data)) self.debug_info = debug_info self.io_start = io_start self.io = list() - self.kernel_end = 0 - for start, name in debug_info['sections'].items(): - if name.startswith('programs'): - self.kernel_end = int(start) - break + + def get_entrypoint(self): + try: + start = self.debug_info['symbols']['kernel'].get('_start', None) + if start is not None: + return start + return self.debug_info['symbols']['kernel'].get('_ftext') + except KeyError: + print(FMT_ERROR + '[MMU] cannot find kernel entry in debug information! Falling back to 0x100' + FMT_NONE) + return 0x100 @lru_cache(maxsize=2048) def read_ins(self, addr: int) -> ElfInstruction: @@ -66,13 +78,6 @@ class ContinuousMMU(MMU): print(FMT_ERROR + "[MMU] possible null dereference (write {:x}) from (pc={:x},sec={},rel={:x})".format( addr, pc, text_sec.owner + ':' + text_sec.name, pc - text_sec.base ) + FMT_NONE) - if addr < self.kernel_end: - if self.cpu.mode != PrivModes.MACHINE: - pc = self.cpu.pc - text_sec = self.get_sec_containing(pc) - print(FMT_ERROR + "[MMU] kernel access to {:x} from outside kernel mode! (pc={:x},sec={},rel={:x})".format( - addr, pc, text_sec.owner + ':' + text_sec.name, pc - text_sec.base - ) + FMT_NONE) if addr >= self.io_start: return self.io_at(addr).write(addr, data, size) @@ -88,7 +93,7 @@ class ContinuousMMU(MMU): self.io.append(io) def __repr__(self): - return "ImageMMU()" + return "MemoryImageMMU()" @lru_cache(maxsize=32) def get_sec_containing(self, addr: int) -> Optional[LoadedMemorySection]: diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index aec1cd6..1b92e1c 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -8,7 +8,7 @@ import time from riscemu.CPU import * from .CSR import CSR from .ElfLoader import ElfExecutable -from .ImageLoader import ContinuousMMU +from .ImageLoader import MemoryImageMMU from .Exceptions import * from .PrivMMU import PrivMMU from ..IO import TextIO @@ -47,22 +47,19 @@ class PrivCPU(CPU): the equivalent of "1 byte" (this is actually impossible) """ - def __init__(self, conf): + def __init__(self, conf, mmu: PrivMMU): super().__init__(conf, [PrivRV32I, RV32M]) self.mode: PrivModes = PrivModes.MACHINE - with open('mem.img', 'rb') as memf: - data = memf.read() - with open('mem.img.dbg', 'r') as dbgf: - debug_info = json.load(dbgf) + mmu.set_cpu(self) + self.pc = mmu.get_entrypoint() + self.mmu = mmu - self.mmu = ContinuousMMU(data, debug_info, self) - self.pc = 0x100 - self.mmu.add_io(TextIO.TextIO(0xff0000, 64)) - self.syscall_int = None + if hasattr(self.mmu, 'add_io'): + self.mmu.add_io(TextIO.TextIO(0xff0000, 64)) + self.syscall_int = None self.launch_debug = False - self.pending_traps: List[CpuTrap] = list() self._time_start = 0 @@ -87,7 +84,7 @@ class PrivCPU(CPU): self.launch_debug = True self.pc += self.INS_XLEN else: - print(FMT_ERROR + "[CPU] excpetion caught at 0x{:08X}: {}:".format(self.pc - 1, ins) + FMT_NONE) + print(FMT_ERROR + "[CPU] exception caught at 0x{:08X}: {}:".format(self.pc - 1, ins) + FMT_NONE) print(ex.message()) if self.conf.debug_on_exception: self.launch_debug = True diff --git a/riscemu/priv/PrivMMU.py b/riscemu/priv/PrivMMU.py index 0e8bb40..39179f9 100644 --- a/riscemu/priv/PrivMMU.py +++ b/riscemu/priv/PrivMMU.py @@ -1,13 +1,29 @@ from ..MMU import * +from abc import abstractmethod import typing from .ElfLoader import ElfExecutable +if typing.TYPE_CHECKING: + from .PrivCPU import PrivCPU + class PrivMMU(MMU): + cpu: 'PrivCPU' + + @abstractmethod + def get_entrypoint(self) -> int: + raise + + def set_cpu(self, cpu: 'PrivCPU'): + self.cpu = cpu + + +class LoadedElfMMU(PrivMMU): def __init__(self, elf: ElfExecutable): - super(PrivMMU, self).__init__(conf=RunConfig()) + super().__init__(conf=RunConfig()) + self.entrypoint = elf.symbols['_start'] self.binaries.append(elf) for sec in elf.sections: @@ -18,3 +34,6 @@ class PrivMMU(MMU): def allocate_section(self, name: str, req_size: int, flag: MemoryFlags): raise NotImplementedError("Not supported!") + + def get_entrypoint(self): + return self.entrypoint diff --git a/riscemu/priv/__main__.py b/riscemu/priv/__main__.py index 47820b7..803487c 100644 --- a/riscemu/priv/__main__.py +++ b/riscemu/priv/__main__.py @@ -1,4 +1,7 @@ from .PrivCPU import PrivCPU, RunConfig +from .ImageLoader import MemoryImageMMU +from .PrivMMU import LoadedElfMMU +from .ElfLoader import ElfExecutable from ..Tokenizer import RiscVInput from ..ExecutableParser import ExecutableParser @@ -8,14 +11,23 @@ import sys if __name__ == '__main__': import argparse - #parser = argparse.ArgumentParser(description='RISC-V privileged architecture emulator', prog='riscemu') + parser = argparse.ArgumentParser(description='RISC-V privileged architecture emulator', prog='riscemu') - #parser.add_argument('--kernel', type=str, help='Kernel elf loaded with user programs', nargs='?') - #parser.add_argument('--image', type=str, help='Memory image containing kernel', nargs='?') + parser.add_argument('--kernel', type=str, help='Kernel elf loaded with user programs', nargs='?') + parser.add_argument('--image', type=str, help='Memory image containing kernel', nargs='?') - #args = parser.parse_args() + args = parser.parse_args() + mmu = None - cpu = PrivCPU(RunConfig()) + if args.kernel is not None: + mmu = LoadedElfMMU(ElfExecutable(args.kernel)) + elif args.image is not None: + mmu = MemoryImageMMU(args.image) + + if mmu is None: + print("You must specify one of --kernel or --image for running in privilege mode!") + + cpu = PrivCPU(RunConfig(), mmu) cpu.run() -- 2.40.1 From ca71e196c291bd45ad9dc93e055e3435947328d8 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Mon, 30 Aug 2021 19:40:13 +0200 Subject: [PATCH 66/69] added verbose flag and improved verbose output --- riscemu/IO/TextIO.py | 1 + riscemu/priv/PrivCPU.py | 10 +++++----- riscemu/priv/PrivRV32I.py | 11 +++++++++++ riscemu/priv/__main__.py | 6 ++++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/riscemu/IO/TextIO.py b/riscemu/IO/TextIO.py index b51813b..d48d465 100644 --- a/riscemu/IO/TextIO.py +++ b/riscemu/IO/TextIO.py @@ -31,6 +31,7 @@ def _window_loop(textIO: 'TextIO'): except ImportError: print("[TextIO] module works best with PySimpleGui!") + textIO.set_sg_window(None) class TextIO(IOModule): diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 1b92e1c..424c51d 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -111,10 +111,10 @@ class PrivCPU(CPU): def get_tokenizer(self, tokenizer_input): raise NotImplementedError("Not supported!") - def run(self): + def run(self, verbose: bool = False): print(FMT_CPU + '[CPU] Started running from 0x{:08X} ({})'.format(self.pc, "kernel") + FMT_NONE) self._time_start = time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._run() + self._run(verbose) def _init_csr(self): # set up CSR @@ -170,7 +170,7 @@ class PrivCPU(CPU): self._timer_step() self._check_interrupt() ins = self.mmu.read_ins(self.pc) - if verbose: + if verbose and self.mode == PrivModes.USER: print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins)) self.run_instruction(ins) self.pc += self.INS_XLEN @@ -190,8 +190,8 @@ class PrivCPU(CPU): # select best interrupt # TODO: actually select based on the official ranking trap = self.pending_traps.pop() # use the most recent trap - if not isinstance(trap, TimerInterrupt): - print(FMT_CPU + "[CPU] taking trap {}!".format(trap) + FMT_NONE) + if not isinstance(trap, TimerInterrupt) or True: + print(FMT_CPU + "[CPU] [{}] taking trap {}!".format(self.cycle, trap) + FMT_NONE) if trap.priv != PrivModes.MACHINE: print(FMT_CPU + "[CPU] Trap not targeting machine mode encountered! - undefined behaviour!" + FMT_NONE) diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index c61c344..cc7c26b 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -8,6 +8,7 @@ from ..instructions.RV32I import * from ..Exceptions import INS_NOT_IMPLEMENTED from .Exceptions import * from .privmodes import PrivModes +from ..colors import FMT_CPU, FMT_NONE import typing if typing.TYPE_CHECKING: @@ -78,6 +79,16 @@ class PrivRV32I(RV32I): mepc = self.cpu.csr.get('mepc') self.cpu.pc = mepc + sec = self.mmu.get_sec_containing(mepc) + if sec is not None: + print(FMT_CPU + "[CPU] [{}] returning to mode: {} in binary {}, section {}, addr 0x{:x}".format( + self.cpu.cycle, + PrivModes(mpp), + sec.owner, + sec.name, + mepc + ) + FMT_NONE) + def instruction_uret(self, ins: 'LoadedInstruction'): raise IllegalInstructionTrap(ins) diff --git a/riscemu/priv/__main__.py b/riscemu/priv/__main__.py index 803487c..f9d3640 100644 --- a/riscemu/priv/__main__.py +++ b/riscemu/priv/__main__.py @@ -16,6 +16,8 @@ if __name__ == '__main__': parser.add_argument('--kernel', type=str, help='Kernel elf loaded with user programs', nargs='?') parser.add_argument('--image', type=str, help='Memory image containing kernel', nargs='?') + parser.add_argument('-v', '--verbose', help="Verbose output", action='store_true') + args = parser.parse_args() mmu = None @@ -26,10 +28,10 @@ if __name__ == '__main__': if mmu is None: print("You must specify one of --kernel or --image for running in privilege mode!") + sys.exit(1) cpu = PrivCPU(RunConfig(), mmu) - - cpu.run() + cpu.run(args.verbose) -- 2.40.1 From 3033eb998549309dd5a24acfce545aa8a9c4b9dc Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Mon, 30 Aug 2021 20:10:22 +0200 Subject: [PATCH 67/69] tranlsation from absolute addressed to symbol-relative names for debugging --- riscemu/priv/ImageLoader.py | 12 +++++++++++- riscemu/priv/PrivMMU.py | 2 ++ riscemu/priv/PrivRV32I.py | 8 +++----- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/riscemu/priv/ImageLoader.py b/riscemu/priv/ImageLoader.py index 50cc25e..88c8f6b 100644 --- a/riscemu/priv/ImageLoader.py +++ b/riscemu/priv/ImageLoader.py @@ -98,7 +98,7 @@ class MemoryImageMMU(PrivMMU): @lru_cache(maxsize=32) def get_sec_containing(self, addr: int) -> Optional[LoadedMemorySection]: next_sec = len(self.data) - for sec_addr, name in sorted(self.debug_info['sections'].items(), key=lambda x: int(x[0]), reverse=True): + for sec_addr, name in reversed(self.debug_info['sections'].items()): if addr >= int(sec_addr): owner, name = name.split(':') base = int(sec_addr) @@ -107,3 +107,13 @@ class MemoryImageMMU(PrivMMU): return ElfLoadedMemorySection(name, base, size, self.data[base:next_sec], flags, owner) else: next_sec = int(sec_addr) + + def translate_address(self, addr: int): + sec = self.get_sec_containing(addr) + if sec.name == '.empty': + return "" + symbs = self.debug_info['symbols'][sec.owner] + for sym, val in reversed(symbs.items()): + if addr >= val: + return "{}{:+x} ({}:{})".format(sym, addr - val, sec.owner, sec.name) + return "{}:{}{:+x}".format(sec.owner, sec.name, addr - sec.base) diff --git a/riscemu/priv/PrivMMU.py b/riscemu/priv/PrivMMU.py index 39179f9..28f2ecf 100644 --- a/riscemu/priv/PrivMMU.py +++ b/riscemu/priv/PrivMMU.py @@ -19,6 +19,8 @@ class PrivMMU(MMU): def set_cpu(self, cpu: 'PrivCPU'): self.cpu = cpu + def translate_address(self, addr: int): + return "" class LoadedElfMMU(PrivMMU): def __init__(self, elf: ElfExecutable): diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index cc7c26b..ef56a75 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -81,12 +81,10 @@ class PrivRV32I(RV32I): sec = self.mmu.get_sec_containing(mepc) if sec is not None: - print(FMT_CPU + "[CPU] [{}] returning to mode: {} in binary {}, section {}, addr 0x{:x}".format( + print(FMT_CPU + "[CPU] [{}] returning to mode {} in {}".format( self.cpu.cycle, - PrivModes(mpp), - sec.owner, - sec.name, - mepc + PrivModes(mpp).name, + self.mmu.translate_address(mepc) ) + FMT_NONE) def instruction_uret(self, ins: 'LoadedInstruction'): -- 2.40.1 From 0c96a87dcb37733764f055de62983b12f80ec216 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Fri, 3 Sep 2021 14:59:34 +0200 Subject: [PATCH 68/69] added RV32A extension, only missing LR.W and SC.W --- riscemu/decoder/decoder.py | 5 ++ riscemu/decoder/formats.py | 3 +- riscemu/decoder/instruction_table.py | 12 +++++ riscemu/instructions/RV32A.py | 78 ++++++++++++++++++++++++++++ riscemu/instructions/__init__.py | 3 +- riscemu/priv/PrivCPU.py | 4 +- 6 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 riscemu/instructions/RV32A.py diff --git a/riscemu/decoder/decoder.py b/riscemu/decoder/decoder.py index 267fdf6..4bdaed8 100644 --- a/riscemu/decoder/decoder.py +++ b/riscemu/decoder/decoder.py @@ -54,6 +54,11 @@ def name_from_insn(ins: int): raise RuntimeError(f"Invalid instruction in ebreak/ecall region: {ins:x}") fun7 = funct7(ins) + if opcode == 0b1011 and fun3 == 0b10: + # ignore the two aq/lr bits located in the fun7 block + # riscemu has no memory reordering, therefore we don't need to look at these bits ever + fun7 = fun7 >> 2 + if fun7 in dec: if opcode == 0x0C or (opcode == 0x04 and fun3 == 5): dec = dec[fun7] diff --git a/riscemu/decoder/formats.py b/riscemu/decoder/formats.py index 9d3e544..2f9af85 100644 --- a/riscemu/decoder/formats.py +++ b/riscemu/decoder/formats.py @@ -112,5 +112,6 @@ INSTRUCTION_ARGS_DECODER: Dict[int, Callable[[int], List[int]]] = { 0x18: decode_b, 0x19: decode_i, 0x1b: decode_j, - 0x1c: decode_i_unsigned + 0x1c: decode_i_unsigned, + 0b1011: decode_r } diff --git a/riscemu/decoder/instruction_table.py b/riscemu/decoder/instruction_table.py index 4ab4417..84dbd2e 100644 --- a/riscemu/decoder/instruction_table.py +++ b/riscemu/decoder/instruction_table.py @@ -67,3 +67,15 @@ RV32[0x0C][5][1] = "divu" RV32[0x0C][6][1] = "rem" RV32[0x0C][7][1] = "remu" +# rv32a +RV32[0b1011][0b10][0b00010] = "lr.w" +RV32[0b1011][0b10][0b00011] = "sc.w" +RV32[0b1011][0b10][0b00001] = "amoswap.w" +RV32[0b1011][0b10][0b00000] = "amoadd.w" +RV32[0b1011][0b10][0b00100] = "amoxor.w" +RV32[0b1011][0b10][0b01100] = "amoand.w" +RV32[0b1011][0b10][0b01000] = "amoor.w" +RV32[0b1011][0b10][0b10000] = "amomin.w" +RV32[0b1011][0b10][0b10100] = "amomax.w" +RV32[0b1011][0b10][0b11000] = "amominu.w" +RV32[0b1011][0b10][0b11100] = "amomaxu.w" diff --git a/riscemu/instructions/RV32A.py b/riscemu/instructions/RV32A.py new file mode 100644 index 0000000..9432c83 --- /dev/null +++ b/riscemu/instructions/RV32A.py @@ -0,0 +1,78 @@ +from .InstructionSet import InstructionSet, LoadedInstruction +from ..Exceptions import INS_NOT_IMPLEMENTED +from ..helpers import int_from_bytes, int_to_bytes, to_unsigned, to_signed + + +class RV32A(InstructionSet): + """ + The RV32A instruction set. Currently, load-reserved and store conditionally are not supported + due to limitations in the way the MMU is implemented. Maybe a later implementation will add support + for this? + """ + + def instruction_lr_w(self, ins: 'LoadedInstruction'): + INS_NOT_IMPLEMENTED(ins) + + def instruction_sc_w(self, ins: 'LoadedInstruction'): + INS_NOT_IMPLEMENTED(ins) + + def instruction_amoswap_w(self, ins: 'LoadedInstruction'): + dest, addr, val = self.parse_rd_rs_rs(ins) + if dest == 'zero': + self.mmu.write(addr, int_to_bytes(addr, 4)) + else: + old = int_from_bytes(self.mmu.read(addr, 4)) + self.mmu.write(addr, int_to_bytes(val, 4)) + self.regs.set(dest, old) + + def instruction_amoadd_w(self, ins: 'LoadedInstruction'): + dest, addr, val = self.parse_rd_rs_rs(ins) + old = int_from_bytes(self.mmu.read(addr, 4)) + self.mmu.write(addr, int_to_bytes(old + val, 4)) + self.regs.set(dest, old) + + def instruction_amoand_w(self, ins: 'LoadedInstruction'): + dest, addr, val = self.parse_rd_rs_rs(ins) + old = int_from_bytes(self.mmu.read(addr, 4)) + self.mmu.write(addr, int_to_bytes(old & val, 4)) + self.regs.set(dest, old) + + def instruction_amoor_w(self, ins: 'LoadedInstruction'): + dest, addr, val = self.parse_rd_rs_rs(ins) + old = int_from_bytes(self.mmu.read(addr, 4)) + self.mmu.write(addr, int_to_bytes(old | val, 4)) + self.regs.set(dest, old) + + def instruction_amoxor_w(self, ins: 'LoadedInstruction'): + dest, addr, val = self.parse_rd_rs_rs(ins) + old = int_from_bytes(self.mmu.read(addr, 4)) + self.mmu.write(addr, int_to_bytes(old ^ val, 4)) + self.regs.set(dest, old) + + def instruction_amomax_w(self, ins: 'LoadedInstruction'): + dest, addr, val = self.parse_rd_rs_rs(ins) + old = int_from_bytes(self.mmu.read(addr, 4)) + self.mmu.write(addr, int_to_bytes(max(old, val), 4)) + self.regs.set(dest, old) + + def instruction_amomaxu_w(self, ins: 'LoadedInstruction'): + dest, addr, val = self.parse_rd_rs_rs(ins) + val = to_unsigned(val) + old = int_from_bytes(self.mmu.read(addr, 4), unsigned=True) + + self.mmu.write(addr, int_to_bytes(to_signed(max(old, val)), 4)) + self.regs.set(dest, old) + + def instruction_amomin_w(self, ins: 'LoadedInstruction'): + dest, addr, val = self.parse_rd_rs_rs(ins) + old = int_from_bytes(self.mmu.read(addr, 4)) + self.mmu.write(addr, int_to_bytes(min(old, val), 4)) + self.regs.set(dest, old) + + def instruction_amominu_w(self, ins: 'LoadedInstruction'): + dest, addr, val = self.parse_rd_rs_rs(ins) + val = to_unsigned(val) + old = int_from_bytes(self.mmu.read(addr, 4), unsigned=True) + + self.mmu.write(addr, int_to_bytes(to_signed(min(old, val)), 4)) + self.regs.set(dest, old) diff --git a/riscemu/instructions/__init__.py b/riscemu/instructions/__init__.py index fb6a17c..65bda29 100644 --- a/riscemu/instructions/__init__.py +++ b/riscemu/instructions/__init__.py @@ -9,7 +9,8 @@ This package holds all instruction sets, available to the processor from .InstructionSet import InstructionSet from .RV32M import RV32M from .RV32I import RV32I +from .RV32A import RV32A InstructionSetDict = { - v.__name__: v for v in [RV32I, RV32M] + v.__name__: v for v in [RV32I, RV32M, RV32A] } diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 424c51d..e3886ac 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -14,7 +14,7 @@ from .PrivMMU import PrivMMU from ..IO import TextIO from .PrivRV32I import PrivRV32I from .privmodes import PrivModes -from ..instructions.RV32M import RV32M +from ..instructions import RV32A, RV32M import json if typing.TYPE_CHECKING: @@ -48,7 +48,7 @@ class PrivCPU(CPU): """ def __init__(self, conf, mmu: PrivMMU): - super().__init__(conf, [PrivRV32I, RV32M]) + super().__init__(conf, [PrivRV32I, RV32M, RV32A]) self.mode: PrivModes = PrivModes.MACHINE mmu.set_cpu(self) -- 2.40.1 From 3d4d36bfe44181a7408aa56d08a8f894c11906a2 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Fri, 3 Sep 2021 15:01:55 +0200 Subject: [PATCH 69/69] moved dependency on pyelftools into scoped function where it's used to reduce the number of dependencies required overall --- riscemu/priv/ElfLoader.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/riscemu/priv/ElfLoader.py b/riscemu/priv/ElfLoader.py index 8f71740..9f8a172 100644 --- a/riscemu/priv/ElfLoader.py +++ b/riscemu/priv/ElfLoader.py @@ -1,9 +1,6 @@ from dataclasses import dataclass from typing import List, Dict, Tuple -from elftools.elf.elffile import ELFFile -from elftools.elf.sections import Section, SymbolTableSection - from .Exceptions import * from ..Exceptions import RiscemuBaseException from ..Executable import MemoryFlags, LoadedMemorySection @@ -12,6 +9,9 @@ from ..helpers import FMT_PARSE, FMT_NONE, FMT_GREEN, FMT_BOLD FMT_ELF = FMT_GREEN + FMT_BOLD +if typing.TYPE_CHECKING: + from elftools.elf.elffile import ELFFile + from elftools.elf.sections import Section, SymbolTableSection # This requires pyelftools package! @@ -29,11 +29,18 @@ class ElfExecutable: self.sections_by_name = dict() self.symbols = dict() - with open(name, 'rb') as f: - print(FMT_ELF + "[ElfLoader] Loading elf executable from: {}".format(name) + FMT_NONE) - self._read_elf(ELFFile(f)) + try: + from elftools.elf.elffile import ELFFile + from elftools.elf.sections import Section, SymbolTableSection + + with open(name, 'rb') as f: + print(FMT_ELF + "[ElfLoader] Loading elf executable from: {}".format(name) + FMT_NONE) + self._read_elf(ELFFile(f)) + except ImportError as e: + print(FMT_PARSE + "[ElfLoader] Cannot load elf files without PyElfTools package! You can install them using pip install pyelftools!" + FMT_NONE) + raise e - def _read_elf(self, elf: ELFFile): + def _read_elf(self, elf: 'ELFFile'): if not elf.header.e_machine == 'EM_RISCV': raise InvalidElfException("Not a RISC-V elf file!") if not elf.header.e_ident.EI_CLASS == 'ELFCLASS32': @@ -41,6 +48,7 @@ class ElfExecutable: self.run_ptr = elf.header.e_entry + from elftools.elf.sections import SymbolTableSection for sec in elf.iter_sections(): if isinstance(sec, SymbolTableSection): self._parse_symtab(sec) @@ -51,7 +59,7 @@ class ElfExecutable: self.add_sec(self._lms_from_elf_sec(sec, 'kernel')) - def _lms_from_elf_sec(self, sec: Section, owner: str): + def _lms_from_elf_sec(self, sec: 'Section', owner: str): is_code = sec.name in ('.text',) data = bytearray(sec.data()) flags = MemoryFlags(is_code, is_code) @@ -65,7 +73,7 @@ class ElfExecutable: owner ) - def _parse_symtab(self, symtab: SymbolTableSection): + def _parse_symtab(self, symtab: 'SymbolTableSection'): self.symbols = { sym.name: sym.entry.st_value for sym in symtab.iter_symbols() if sym.name } -- 2.40.1