From 71093fe72f85a4ce207df7ff45d5549f584ffe65 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sun, 27 Mar 2022 15:21:10 +0200 Subject: [PATCH] Maor round of bugfixes and incremental improvements - fixed errors in TextIO and IOModule - moved to Int32 and UInt32 based arithmetic - added a lot of end-to-end and other tests --- .idea/riscemu.iml | 3 + riscemu/CPU.py | 4 +- riscemu/IO/IOModule.py | 30 ++-- riscemu/IO/TextIO.py | 72 ++------ riscemu/MMU.py | 68 ++++++-- riscemu/__init__.py | 2 +- riscemu/assembler.py | 13 +- riscemu/decoder/formatter.py | 2 +- riscemu/helpers.py | 42 +---- riscemu/instructions/RV32A.py | 48 +++--- riscemu/instructions/RV32I.py | 57 +++---- riscemu/instructions/instruction_set.py | 37 ++-- riscemu/priv/CSR.py | 28 +-- riscemu/priv/ElfLoader.py | 4 +- riscemu/priv/Exceptions.py | 5 +- riscemu/priv/ImageLoader.py | 10 +- riscemu/priv/PrivCPU.py | 28 +-- riscemu/priv/PrivRV32I.py | 33 ++-- riscemu/priv/__main__.py | 2 +- riscemu/priv/types.py | 16 +- riscemu/registers.py | 18 +- riscemu/types.py | 216 +++++++++++++++++++++++- setup.py | 2 +- test/__init__.py | 3 +- test/test_helpers.py | 23 --- test/test_integers.py | 19 +++ test/test_isa.py | 2 - 27 files changed, 486 insertions(+), 301 deletions(-) create mode 100644 test/test_integers.py diff --git a/.idea/riscemu.iml b/.idea/riscemu.iml index 8ed6672..71b6faa 100644 --- a/.idea/riscemu.iml +++ b/.idea/riscemu.iml @@ -4,6 +4,9 @@ + + + diff --git a/riscemu/CPU.py b/riscemu/CPU.py index 3b9235d..2fcedd0 100644 --- a/riscemu/CPU.py +++ b/riscemu/CPU.py @@ -17,7 +17,7 @@ from .colors import FMT_CPU, FMT_NONE from .debug import launch_debug_session from .exceptions import RiscemuBaseException, LaunchDebuggerException from .syscall import SyscallInterface, get_syscall_symbols -from .types import CPU, ProgramLoader +from .types import CPU, ProgramLoader, Int32 from .parser import AssemblyFileLoader if typing.TYPE_CHECKING: @@ -107,7 +107,7 @@ class UserModeCPU(CPU): if not self.mmu.load_section(stack_sec, fixed_position=False): return False - self.regs.set('sp', stack_sec.base + stack_sec.size) + self.regs.set('sp', Int32(stack_sec.base + stack_sec.size)) return True @classmethod diff --git a/riscemu/IO/IOModule.py b/riscemu/IO/IOModule.py index 21d6a97..521ae93 100644 --- a/riscemu/IO/IOModule.py +++ b/riscemu/IO/IOModule.py @@ -1,22 +1,22 @@ from abc import ABC, abstractmethod +from typing import Optional +from riscemu.types import MemorySection, MemoryFlags, T_RelativeAddress -class IOModule(ABC): - addr: int - size: int - def __init__(self, addr: int, size: int): - self.addr = addr - self.size = size +class IOModule(MemorySection, ABC): + def __init__(self, name: str, flags: MemoryFlags, size: int, owner: str = 'system', base: int = 0): + super(IOModule, self).__init__(name, flags, size, base, owner, None) - @abstractmethod - def read(self, addr: int, size: int): - pass + def contains(self, addr, size: int = 0): + return self.base <= addr < self.base + self.size and \ + self.base <= addr + size <= self.base + self.size - @abstractmethod - def write(self, addr: int, data: bytearray, size: int): - pass + def dump(self, start: T_RelativeAddress, end: Optional[T_RelativeAddress] = None, fmt: str = 'hex', + bytes_per_row: int = 16, rows: int = 10, group: int = 4): + print(self) - def contains(self, addr, size: int = 0): - return self.addr <= addr < self.addr + self.size and \ - self.addr <= addr + size <= self.addr + self.size + def __repr__(self): + return "{}[{}] at 0x{:0X} (size={}bytes, flags={})".format( + self.__class__.__name__, self.name, self.base, self.size, self.flags + ) \ No newline at end of file diff --git a/riscemu/IO/TextIO.py b/riscemu/IO/TextIO.py index 1a615e0..a57e1ab 100644 --- a/riscemu/IO/TextIO.py +++ b/riscemu/IO/TextIO.py @@ -1,70 +1,28 @@ from .IOModule import IOModule from ..priv.Exceptions import InstructionAccessFault -from ..helpers import int_from_bytes -from threading import Thread -import time +from ..types import T_RelativeAddress, Instruction, MemoryFlags, Int32 -def _window_loop(textIO: 'TextIO'): - try: - import PySimpleGUI as sg - - logs = sg.Text(font="monospace") - col = sg.Column([[logs]], size=(640, 400), 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] window disabled - please install PySimpleGui!") - textIO.set_sg_window(None) - class TextIO(IOModule): - def __init__(self, addr: int, buflen: int = 128): - super(TextIO, self).__init__(addr, buflen + 4) + def read_ins(self, offset: T_RelativeAddress) -> Instruction: + raise InstructionAccessFault(self.base + offset) + + def __init__(self, base: int, buflen: int = 128): + super(TextIO, self).__init__('TextIO', MemoryFlags(False, False), buflen + 4, base=base) 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) + raise InstructionAccessFault(self.base + addr) - def write(self, addr: int, data: bytearray, size: int): - if addr == self.addr: + def write(self, addr: int, size: int, data: bytearray): + if addr == 0: if size > 4: raise InstructionAccessFault(addr) - if int_from_bytes(data[0:4]) > 0: + if Int32(data) != 0: self._print() return - buff_start = addr - self.addr - 4 + buff_start = addr - 4 self.buff[buff_start:buff_start + size] = data[0:size] def _print(self): @@ -83,10 +41,4 @@ class TextIO(IOModule): 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)) - + print("[TextIO:{:x}] {}".format(self.base, text)) diff --git a/riscemu/MMU.py b/riscemu/MMU.py index eeb75d7..fdbf822 100644 --- a/riscemu/MMU.py +++ b/riscemu/MMU.py @@ -4,13 +4,13 @@ RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Union from .colors import * from .exceptions import InvalidAllocationException, MemoryAccessException -from .helpers import align_addr, int_from_bytes +from .helpers import align_addr from .types import Instruction, MemorySection, MemoryFlags, T_AbsoluteAddress, \ - Program, InstructionContext + Program, InstructionContext, Int32 class MMU: @@ -85,7 +85,7 @@ class MMU: raise RuntimeError("No next instruction available!") return sec.read_ins(addr - sec.base) - def read(self, addr: int, size: int) -> bytearray: + def read(self, addr: Union[int, Int32], size: int) -> bytearray: """ Read size bytes of memory at addr @@ -93,13 +93,16 @@ class MMU: :param size: The number of bytes to read :return: The bytearray at addr """ + if isinstance(addr, Int32): + breakpoint() + addr = addr.unsigned_value sec = self.get_sec_containing(addr) if sec is None: print(FMT_MEM + "[MMU] Trying to read data form invalid region at 0x{:x}! ".format(addr) + FMT_NONE) raise MemoryAccessException("region is non-initialized!", addr, size, 'read') return sec.read(addr - sec.base, size) - def write(self, addr: int, size: int, data): + def write(self, addr: int, size: int, data: bytearray): """ Write bytes into memory @@ -137,32 +140,51 @@ class MMU: print(FMT_MEM + "[MMU] Lookup for symbol {}:".format(symb) + FMT_NONE) if symb in self.global_symbols: print(" Found global symbol {}: 0x{:X}".format(symb, self.global_symbols[symb])) - for section in self.sections: - if symb in section.context.labels: - print(" Found local labels {}: 0x{:X} in {}".format(symb, section.context.labels[symb], section.name)) + for bin in self.programs: + if symb in bin.context.labels: + print(" Found local labels {}: 0x{:X} in {}".format(symb, bin.context.labels[symb], bin.name)) - def read_int(self, addr: int) -> int: - return int_from_bytes(self.read(addr, 4)) + def read_int(self, addr: int) -> Int32: + return Int32(self.read(addr, 4)) def translate_address(self, address: T_AbsoluteAddress) -> str: - # FIXME: proper implementation using the debug info - return str(address) + sec = self.get_sec_containing(address) + if not sec: + return "unknown at 0x{:0x}".format(address) + + bin = self.get_bin_containing(address) + secs = set(sec.name for sec in bin.sections) if bin else [] + + def key(x): + name, val = x + + if name in secs or val > address: + return float('inf') + return address - val + + name, val = min(sec.context.labels.items(), key=key, default=('.empty', None)) + if val is None: + return "unknown at 0x{:0x}".format(address) + + return str('{}:{} at {} (0x{:0x}) + 0x{:0x}'.format( + sec.owner, sec.name, name, val, address - val + )) def has_continous_free_region(self, start: int, end: int) -> bool: # if we have no sections we are all good if len(self.sections) == 0: return True # if the last section is located before the start we are also good - if start > self.sections[-1].base + self.sections[-1].size: + if start >= self.sections[-1].base + self.sections[-1].size: return True for sec in self.sections: # skip all sections that end before the required start point - if sec.base + sec.size < start: + if sec.base + sec.size <= start: continue # we now have the first section that doesn't end **before** the start point # if this section starts after the specified end, we are good - if sec.base > end: + if sec.base >= end: return True # otherwise we can't continue return False @@ -230,7 +252,8 @@ class MMU: return self.sections[-1].base + self.sections[-1].size def __repr__(self): - return "MMU(\n\t{}\n)".format( + return "{}(\n\t{}\n)".format( + self.__class__.__name__, "\n\t".join(repr(x) for x in self.programs) ) @@ -241,3 +264,16 @@ class MMU: return sec.context return InstructionContext() + + def report_addr(self, addr: T_AbsoluteAddress): + sec = self.get_sec_containing(addr) + if not sec: + print("addr is in no section!") + return + owner = [b for b in self.programs if b.name == sec.owner] + if owner: + print("owned by: {}".format(owner[0])) + + + print("{}: 0x{:0x} + 0x{:0x}".format(name, val, addr - val)) + diff --git a/riscemu/__init__.py b/riscemu/__init__.py index 22d1f8b..6c39581 100644 --- a/riscemu/__init__.py +++ b/riscemu/__init__.py @@ -25,4 +25,4 @@ from .parser import tokenize, parse_tokens, AssemblyFileLoader __author__ = "Anton Lydike " __copyright__ = "Copyright 2021 Anton Lydike" -__version__ = '1.0.0' \ No newline at end of file +__version__ = '2.0.0a1' diff --git a/riscemu/assembler.py b/riscemu/assembler.py index 768d504..8e0fca0 100644 --- a/riscemu/assembler.py +++ b/riscemu/assembler.py @@ -1,13 +1,13 @@ -from typing import Optional, Tuple, Union, List from enum import Enum, auto +from typing import List from typing import Optional, Tuple, Union -from .helpers import parse_numeric_argument, align_addr, int_to_bytes, get_section_base_name -from .types import Program, T_RelativeAddress, InstructionContext, Instruction +from .base import BinaryDataMemorySection, InstructionMemorySection from .colors import FMT_PARSE, FMT_NONE -from .exceptions import ParseException, ASSERT_LEN, ASSERT_NOT_NULL +from .exceptions import ParseException, ASSERT_LEN +from .helpers import parse_numeric_argument, align_addr, get_section_base_name from .tokenizer import Token -from .base import BinaryDataMemorySection, InstructionMemorySection +from .types import Program, T_RelativeAddress, InstructionContext, Instruction, UInt32, Int32 INSTRUCTION_SECTION_NAMES = ('.text', '.init', '.fini') """ @@ -96,7 +96,6 @@ class ParseContext: if is_relative: self.program.relative_labels.add(name) - def __repr__(self): return "{}(\n\tsetion={},\n\tprogram={}\n)".format( self.__class__.__name__, self.section, self.program @@ -176,7 +175,7 @@ class AssemblerDirectives: if content is None: content = bytearray(size) if isinstance(content, int): - content = int_to_bytes(content, size, unsigned) + content = bytearray(content.to_bytes(size, 'little', signed=not unsigned)) context.section.data += content diff --git a/riscemu/decoder/formatter.py b/riscemu/decoder/formatter.py index c1c7955..0d7d304 100644 --- a/riscemu/decoder/formatter.py +++ b/riscemu/decoder/formatter.py @@ -24,7 +24,7 @@ def format_ins(ins: int, name: str, fmt: str = 'int'): return name if opcode in (0x8, 0x0): r1, r2, imm = decoder(ins) - return f"{name:<7} {r1}, {imm}({r2})" + return f"{name:<7} {RISCV_REGS[r1]}, {imm}({RISCV_REGS[r2]})" elif decoder in (decode_i, decode_i_unsigned, decode_b, decode_i_shamt, decode_s): r1, r2, imm = decoder(ins) r1, r2 = RISCV_REGS[r1], RISCV_REGS[r2] diff --git a/riscemu/helpers.py b/riscemu/helpers.py index 3048cb1..82774d1 100644 --- a/riscemu/helpers.py +++ b/riscemu/helpers.py @@ -5,9 +5,11 @@ SPDX-License-Identifier: MIT """ from math import log10, ceil -from .exceptions import * from typing import Iterable, Iterator, TypeVar, Generic, List, Optional +from .exceptions import * +import types + def align_addr(addr: int, to_bytes: int = 8) -> int: """ @@ -28,40 +30,6 @@ def parse_numeric_argument(arg: str) -> int: raise ParseException('Invalid immediate argument \"{}\", maybe missing symbol?'.format(arg), (arg, ex)) -def int_to_bytes(val, bytes=4, unsigned=False) -> bytearray: - """ - int -> byte (two's complement) - """ - if unsigned and val < 0: - raise NumberFormatException("unsigned negative number!") - return bytearray(to_unsigned(val, bytes).to_bytes(bytes, 'little')) - - -def int_from_bytes(bytes, unsigned=False) -> int: - """ - byte -> int (two's complement) - """ - num = int.from_bytes(bytes, 'little') - - if unsigned: - return num - - return to_signed(num, len(bytes)) - - -def to_unsigned(num: int, bytes=4) -> int: - if num < 0: - return (2 ** (bytes * 8)) + num - return num - - -def to_signed(num: int, bytes=4) -> int: - if num >> (bytes * 8 - 1): - return num - 2 ** (8 * bytes) - return num - - - def create_chunks(my_list, chunk_size): """Split a list like [a,b,c,d,e,f,g,h,i,j,k,l,m] into e.g. [[a,b,c,d],[e,f,g,h],[i,j,k,l],[m]]""" return [my_list[i:i + chunk_size] for i in range(0, len(my_list), chunk_size)] @@ -87,10 +55,10 @@ def format_bytes(byte_arr: bytearray, fmt: str, group: int = 1, highlight: int = return highlight_in_list(['0x{}'.format(ch.hex()) for ch in chunks], highlight) if fmt == 'int': spc = str(ceil(log10(2 ** (group * 8 - 1))) + 1) - return highlight_in_list([('{:0' + spc + 'd}').format(int_from_bytes(ch)) for ch in chunks], highlight) + return highlight_in_list([('{:0' + spc + 'd}').format(types.Int32(ch)) for ch in chunks], highlight) if fmt == 'uint': spc = str(ceil(log10(2 ** (group * 8)))) - return highlight_in_list([('{:0' + spc + 'd}').format(int_from_bytes(ch, unsigned=True)) for ch in chunks], + return highlight_in_list([('{:0' + spc + 'd}').format(types.UInt32(ch)) for ch in chunks], highlight) if fmt == 'ascii': return "".join(repr(chr(b))[1:-1] for b in byte_arr) diff --git a/riscemu/instructions/RV32A.py b/riscemu/instructions/RV32A.py index 44c3f32..c7f7c15 100644 --- a/riscemu/instructions/RV32A.py +++ b/riscemu/instructions/RV32A.py @@ -1,6 +1,6 @@ from .instruction_set import InstructionSet, Instruction from ..exceptions import INS_NOT_IMPLEMENTED -from ..helpers import int_from_bytes, int_to_bytes, to_unsigned, to_signed +from ..types import Int32, UInt32 class RV32A(InstructionSet): @@ -19,60 +19,60 @@ class RV32A(InstructionSet): def instruction_amoswap_w(self, ins: 'Instruction'): dest, addr, val = self.parse_rd_rs_rs(ins) if dest == 'zero': - self.mmu.write(addr, int_to_bytes(addr, 4)) + self.mmu.write(addr, val.to_bytes()) else: - old = int_from_bytes(self.mmu.read(addr, 4)) - self.mmu.write(addr, int_to_bytes(val, 4)) + old = Int32(self.mmu.read(addr, 4)) + self.mmu.write(addr, val.to_bytes()) self.regs.set(dest, old) def instruction_amoadd_w(self, ins: 'Instruction'): dest, addr, val = self.parse_rd_rs_rs(ins) - old = int_from_bytes(self.mmu.read(addr, 4)) - self.mmu.write(addr, int_to_bytes(old + val, 4)) + old = Int32(self.mmu.read(addr, 4)) + self.mmu.write(addr, (old + val).to_bytes(4)) self.regs.set(dest, old) def instruction_amoand_w(self, ins: 'Instruction'): dest, addr, val = self.parse_rd_rs_rs(ins) - old = int_from_bytes(self.mmu.read(addr, 4)) - self.mmu.write(addr, int_to_bytes(old & val, 4)) + old = Int32(self.mmu.read(addr, 4)) + self.mmu.write(addr, (old & val).to_bytes(4)) self.regs.set(dest, old) def instruction_amoor_w(self, ins: 'Instruction'): dest, addr, val = self.parse_rd_rs_rs(ins) - old = int_from_bytes(self.mmu.read(addr, 4)) - self.mmu.write(addr, int_to_bytes(old | val, 4)) + old = Int32(self.mmu.read(addr, 4)) + self.mmu.write(addr, (old | val).to_bytes(4)) self.regs.set(dest, old) def instruction_amoxor_w(self, ins: 'Instruction'): dest, addr, val = self.parse_rd_rs_rs(ins) - old = int_from_bytes(self.mmu.read(addr, 4)) - self.mmu.write(addr, int_to_bytes(old ^ val, 4)) + old = Int32(self.mmu.read(addr, 4)) + self.mmu.write(addr, (old ^ val).to_bytes(4)) self.regs.set(dest, old) def instruction_amomax_w(self, ins: 'Instruction'): dest, addr, val = self.parse_rd_rs_rs(ins) - old = int_from_bytes(self.mmu.read(addr, 4)) - self.mmu.write(addr, int_to_bytes(max(old, val), 4)) + old = Int32(self.mmu.read(addr, 4)) + self.mmu.write(addr, max(old, val).to_bytes(4)) self.regs.set(dest, old) def instruction_amomaxu_w(self, ins: 'Instruction'): - dest, addr, val = self.parse_rd_rs_rs(ins) - val = to_unsigned(val) - old = int_from_bytes(self.mmu.read(addr, 4), unsigned=True) + val: UInt32 + dest, addr, val = self.parse_rd_rs_rs(ins, signed=False) + old = UInt32(self.mmu.read(addr, 4)) - self.mmu.write(addr, int_to_bytes(to_signed(max(old, val)), 4)) + self.mmu.write(addr, max(old, val).to_bytes()) self.regs.set(dest, old) def instruction_amomin_w(self, ins: 'Instruction'): 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)) + old = Int32(self.mmu.read(addr, 4)) + self.mmu.write(addr, min(old, val).to_bytes(4)) self.regs.set(dest, old) def instruction_amominu_w(self, ins: 'Instruction'): - dest, addr, val = self.parse_rd_rs_rs(ins) - val = to_unsigned(val) - old = int_from_bytes(self.mmu.read(addr, 4), unsigned=True) + val: UInt32 + dest, addr, val = self.parse_rd_rs_rs(ins, signed=False) + old = UInt32(self.mmu.read(addr, 4)) - self.mmu.write(addr, int_to_bytes(to_signed(min(old, val)), 4)) + self.mmu.write(addr, min(old, val).to_bytes(4)) self.regs.set(dest, old) diff --git a/riscemu/instructions/RV32I.py b/riscemu/instructions/RV32I.py index 291ccbe..26d0bd9 100644 --- a/riscemu/instructions/RV32I.py +++ b/riscemu/instructions/RV32I.py @@ -7,12 +7,11 @@ SPDX-License-Identifier: MIT from .instruction_set import * from ..CPU import UserModeCPU -from ..helpers import int_from_bytes, int_to_bytes, to_unsigned, to_signed from ..colors import FMT_DEBUG, FMT_NONE from ..debug import launch_debug_session from ..exceptions import LaunchDebuggerException from ..syscall import Syscall -from ..types import Instruction +from ..types import Instruction, Int32, UInt32 class RV32I(InstructionSet): @@ -26,35 +25,35 @@ class RV32I(InstructionSet): def instruction_lb(self, ins: 'Instruction'): rd, addr = self.parse_mem_ins(ins) - self.regs.set(rd, int_from_bytes(self.mmu.read(addr, 1))) + self.regs.set(rd, Int32(self.mmu.read(addr.unsigned_value, 1))) def instruction_lh(self, ins: 'Instruction'): rd, addr = self.parse_mem_ins(ins) - self.regs.set(rd, int_from_bytes(self.mmu.read(addr, 2))) + self.regs.set(rd, Int32(self.mmu.read(addr.unsigned_value, 2))) def instruction_lw(self, ins: 'Instruction'): rd, addr = self.parse_mem_ins(ins) - self.regs.set(rd, int_from_bytes(self.mmu.read(addr, 4))) + self.regs.set(rd, Int32(self.mmu.read(addr.unsigned_value, 4))) def instruction_lbu(self, ins: 'Instruction'): rd, addr = self.parse_mem_ins(ins) - self.regs.set(rd, int_from_bytes(self.mmu.read(addr, 1), unsigned=True)) + self.regs.set(rd, UInt32(self.mmu.read(addr.unsigned_value, 1))) def instruction_lhu(self, ins: 'Instruction'): rd, addr = self.parse_mem_ins(ins) - self.regs.set(rd, int_from_bytes(self.mmu.read(addr, 2), unsigned=True)) + self.regs.set(rd, UInt32(self.mmu.read(addr.unsigned_value, 2))) def instruction_sb(self, ins: 'Instruction'): rd, addr = self.parse_mem_ins(ins) - self.mmu.write(addr, 1, int_to_bytes(self.regs.get(rd), 1)) + self.mmu.write(addr.value, 1, self.regs.get(rd).to_bytes(1)) def instruction_sh(self, ins: 'Instruction'): rd, addr = self.parse_mem_ins(ins) - self.mmu.write(addr, 2, int_to_bytes(self.regs.get(rd), 2)) + self.mmu.write(addr.value, 2, self.regs.get(rd).to_bytes(2)) def instruction_sw(self, ins: 'Instruction'): rd, addr = self.parse_mem_ins(ins) - self.mmu.write(addr, 4, int_to_bytes(self.regs.get(rd), 4)) + self.mmu.write(addr.value, 4, self.regs.get(rd).to_bytes(4)) def instruction_sll(self, ins: 'Instruction'): ASSERT_LEN(ins.args, 3) @@ -63,7 +62,7 @@ class RV32I(InstructionSet): src2 = ins.get_reg(2) self.regs.set( dst, - to_signed(to_unsigned(self.regs.get(src1)) << (self.regs.get(src2) & 0b11111)) + self.regs.get(src1) << (self.regs.get(src2) & 0b11111) ) def instruction_slli(self, ins: 'Instruction'): @@ -73,7 +72,7 @@ class RV32I(InstructionSet): imm = ins.get_imm(2) self.regs.set( dst, - to_signed(to_unsigned(self.regs.get(src1)) << (imm & 0b11111)) + self.regs.get(src1) << (imm & 0b11111) ) def instruction_srl(self, ins: 'Instruction'): @@ -83,7 +82,7 @@ class RV32I(InstructionSet): src2 = ins.get_reg(2) self.regs.set( dst, - to_signed(to_unsigned(self.regs.get(src1)) >> (self.regs.get(src2) & 0b11111)) + self.regs.get(src1).shift_right_logical(self.regs.get(src2) & 0b11111) ) def instruction_srli(self, ins: 'Instruction'): @@ -93,7 +92,7 @@ class RV32I(InstructionSet): imm = ins.get_imm(2) self.regs.set( dst, - to_signed(to_unsigned(self.regs.get(src1)) >> (imm & 0b11111)) + self.regs.get(src1).shift_right_logical(imm & 0b11111) ) def instruction_sra(self, ins: 'Instruction'): @@ -142,14 +141,14 @@ class RV32I(InstructionSet): def instruction_lui(self, ins: 'Instruction'): ASSERT_LEN(ins.args, 2) reg = ins.get_reg(0) - imm = ins.get_imm(1) - self.regs.set(reg, imm << 12) + imm = UInt32(ins.get_imm(1)) << 12 + self.regs.set(reg, Int32(imm)) def instruction_auipc(self, ins: 'Instruction'): ASSERT_LEN(ins.args, 2) reg = ins.get_reg(0) - imm = to_unsigned(ins.get_imm(1)) - self.regs.set(reg, self.pc + (imm << 12)) + imm = UInt32(ins.get_imm(1) << 12) + self.regs.set(reg, imm.signed() + self.pc) def instruction_xor(self, ins: 'Instruction'): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) @@ -197,59 +196,59 @@ class RV32I(InstructionSet): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) self.regs.set( rd, - int(rs1 < rs2) + Int32(int(rs1 < rs2)) ) def instruction_slti(self, ins: 'Instruction'): rd, rs1, imm = self.parse_rd_rs_imm(ins) self.regs.set( rd, - int(rs1 < imm) + Int32(int(rs1 < imm)) ) def instruction_sltu(self, ins: 'Instruction'): dst, rs1, rs2 = self.parse_rd_rs_rs(ins, signed=False) self.regs.set( dst, - int(rs1 < rs2) + Int32(int(rs1 < rs2)) ) def instruction_sltiu(self, ins: 'Instruction'): dst, rs1, imm = self.parse_rd_rs_imm(ins, signed=False) self.regs.set( dst, - int(rs1 < imm) + Int32(int(rs1 < imm)) ) def instruction_beq(self, ins: 'Instruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 == rs2: - self.pc = dst + self.pc = dst.unsigned_value def instruction_bne(self, ins: 'Instruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 != rs2: - self.pc = dst + self.pc = dst.unsigned_value def instruction_blt(self, ins: 'Instruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 < rs2: - self.pc = dst + self.pc = dst.unsigned_value def instruction_bge(self, ins: 'Instruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 >= rs2: - self.pc = dst + self.pc = dst.unsigned_value def instruction_bltu(self, ins: 'Instruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) if rs1 < rs2: - self.pc = dst + self.pc = dst.unsigned_value def instruction_bgeu(self, ins: 'Instruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) if rs1 >= rs2: - self.pc = dst + self.pc = dst.unsigned_value # technically deprecated def instruction_j(self, ins: 'Instruction'): @@ -277,7 +276,7 @@ class RV32I(InstructionSet): def instruction_ret(self, ins: 'Instruction'): ASSERT_LEN(ins.args, 0) - self.pc = self.regs.get('ra') + self.pc = self.regs.get('ra').value def instruction_ecall(self, ins: 'Instruction'): self.instruction_scall(ins) diff --git a/riscemu/instructions/instruction_set.py b/riscemu/instructions/instruction_set.py index 8b277c6..e0d3f06 100644 --- a/riscemu/instructions/instruction_set.py +++ b/riscemu/instructions/instruction_set.py @@ -8,9 +8,8 @@ from typing import Tuple, Callable, Dict from abc import ABC from ..CPU import CPU -from ..helpers import to_unsigned from ..exceptions import ASSERT_LEN, ASSERT_IN -from ..types import Instruction +from ..types import Instruction, Int32, UInt32 class InstructionSet(ABC): @@ -52,7 +51,7 @@ class InstructionSet(ABC): if member.startswith('instruction_'): yield member[12:].replace('_', '.'), getattr(self, member) - def parse_mem_ins(self, ins: 'Instruction') -> Tuple[str, int]: + def parse_mem_ins(self, ins: 'Instruction') -> Tuple[str, Int32]: """ parses both rd, rs, imm and rd, imm(rs) argument format and returns (rd, imm+rs1) (so a register and address tuple for memory instructions) @@ -70,7 +69,7 @@ class InstructionSet(ABC): rd = ins.get_reg(0) return rd, rs + imm - def parse_rd_rs_rs(self, ins: 'Instruction', signed=True) -> Tuple[str, int, int]: + def parse_rd_rs_rs(self, ins: 'Instruction', signed=True) -> Tuple[str, Int32, Int32]: """ Assumes the command is in rd, rs1, rs2 format Returns the name of rd, and the values in rs1 and rs2 @@ -82,10 +81,10 @@ class InstructionSet(ABC): self.get_reg_content(ins, 2) else: return ins.get_reg(0), \ - to_unsigned(self.get_reg_content(ins, 1)), \ - to_unsigned(self.get_reg_content(ins, 2)) + UInt32(self.get_reg_content(ins, 1)), \ + UInt32(self.get_reg_content(ins, 2)) - def parse_rd_rs_imm(self, ins: 'Instruction', signed=True) -> Tuple[str, int, int]: + def parse_rd_rs_imm(self, ins: 'Instruction', signed=True) -> Tuple[str, Int32, Int32]: """ Assumes the command is in rd, rs, imm format Returns the name of rd, the value in rs and the immediate imm @@ -93,28 +92,28 @@ class InstructionSet(ABC): ASSERT_LEN(ins.args, 3) if signed: return ins.get_reg(0), \ - self.get_reg_content(ins, 1), \ - ins.get_imm(2) + Int32(self.get_reg_content(ins, 1)), \ + Int32(ins.get_imm(2)) else: return ins.get_reg(0), \ - to_unsigned(self.get_reg_content(ins, 1)), \ - to_unsigned(ins.get_imm(2)) + UInt32(self.get_reg_content(ins, 1)), \ + UInt32(ins.get_imm(2)) - def parse_rs_rs_imm(self, ins: 'Instruction', signed=True) -> Tuple[int, int, int]: + def parse_rs_rs_imm(self, ins: 'Instruction', signed=True) -> Tuple[Int32, Int32, Int32]: """ Assumes the command is in rs1, rs2, imm format Returns the values in rs1, rs2 and the immediate imm """ if signed: - return self.get_reg_content(ins, 0), \ - self.get_reg_content(ins, 1), \ - ins.get_imm(2) + return Int32(self.get_reg_content(ins, 0)), \ + Int32(self.get_reg_content(ins, 1)), \ + Int32(ins.get_imm(2)) else: - return to_unsigned(self.get_reg_content(ins, 0)), \ - to_unsigned(self.get_reg_content(ins, 1)), \ - to_unsigned(ins.get_imm(2)) + return UInt32(self.get_reg_content(ins, 0)), \ + UInt32(self.get_reg_content(ins, 1)), \ + UInt32(ins.get_imm(2)) - def get_reg_content(self, ins: 'Instruction', ind: int) -> int: + def get_reg_content(self, ins: 'Instruction', ind: int) -> Int32: """ get the register name from ins and then return the register contents """ diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index 4a2cc7b..fbd83c6 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -2,49 +2,49 @@ from typing import Dict, Union, Callable, Optional from collections import defaultdict from .privmodes import PrivModes from .Exceptions import InstructionAccessFault -from ..helpers import to_signed from ..colors import FMT_CSR, FMT_NONE from .CSRConsts import CSR_NAME_TO_ADDR, MSTATUS_LEN_2, MSTATUS_OFFSETS +from ..types import UInt32 class CSR: """ This holds all Control and Status Registers (CSR) """ - regs: Dict[int, int] + regs: Dict[int, UInt32] """ All Control and Status Registers are stored here """ - virtual_regs: Dict[int, Callable[[], int]] + virtual_regs: Dict[int, Callable[[], UInt32]] """ list of virtual CSR registers, with values computed on read """ - listeners: Dict[int, Callable[[int, int], None]] + listeners: Dict[int, Callable[[UInt32, UInt32], None]] - mstatus_cache: Dict[str, int] + mstatus_cache: Dict[str, UInt32] mstatus_cache_dirty = True def __init__(self): - self.regs = defaultdict(lambda: 0) + self.regs = defaultdict(lambda: UInt32(0)) self.listeners = defaultdict(lambda: (lambda x, y: None)) self.virtual_regs = dict() self.mstatus_cache = dict() # TODO: implement write masks (bitmasks which control writeable bits in registers - def set(self, addr: Union[str, int], val: int): + def set(self, addr: Union[str, int], val: Union[int, UInt32]): addr = self._name_to_addr(addr) if addr is None: return - val = to_signed(val) + val = UInt32(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: + def get(self, addr: Union[str, int]) -> UInt32: addr = self._name_to_addr(addr) if addr is None: raise RuntimeError(f"Invalid CSR name: {addr}!") @@ -52,7 +52,7 @@ class CSR: return self.virtual_regs[addr]() return self.regs[addr] - def set_listener(self, addr: Union[str, int], listener: Callable[[int, int], None]): + def set_listener(self, addr: Union[str, int], listener: Callable[[UInt32, UInt32], None]): addr = self._name_to_addr(addr) if addr is None: print("unknown csr address name: {}".format(addr)) @@ -60,7 +60,7 @@ class CSR: self.listeners[addr] = listener # mstatus properties - def set_mstatus(self, name: str, val: int): + def set_mstatus(self, name: str, val: UInt32): """ 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. @@ -79,7 +79,7 @@ class CSR: new_val = erased | (val << off) self.set('mstatus', new_val) - def get_mstatus(self, name) -> int: + def get_mstatus(self, name) -> UInt32: if not self.mstatus_cache_dirty and name in self.mstatus_cache: return self.mstatus_cache[name] @@ -94,7 +94,7 @@ class CSR: return val def callback(self, addr: Union[str, int]): - def inner(func: Callable[[int, int], None]): + def inner(func: Callable[[UInt32, UInt32], None]): self.set_listener(addr, func) return func @@ -121,7 +121,7 @@ class CSR: if addr is None: print("unknown csr address name: {}".format(addr)) - def inner(func: Callable[[], int]): + def inner(func: Callable[[], UInt32]): self.virtual_regs[addr] = func return func diff --git a/riscemu/priv/ElfLoader.py b/riscemu/priv/ElfLoader.py index 48fab49..f8538c6 100644 --- a/riscemu/priv/ElfLoader.py +++ b/riscemu/priv/ElfLoader.py @@ -81,12 +81,14 @@ class ElfBinaryFileLoader(ProgramLoader): ) def _parse_symtab(self, symtab: 'SymbolTableSection'): + from elftools.elf.enums import ENUM_ST_VISIBILITY + for sym in symtab.iter_symbols(): if not sym.name: continue self.program.context.labels[sym.name] = sym.entry.st_value # check if it has st_visibility bit set - if sym.entry.st_shndx == 1: # STB_GLOBAL = 1 + if sym.entry.st_info.bind == 'STB_GLOBAL': self.program.global_labels.add(sym.name) print(FMT_PARSE + "LOADED GLOBAL SYMBOL {}: {}".format(sym.name, sym.entry.st_value) + FMT_NONE) diff --git a/riscemu/priv/Exceptions.py b/riscemu/priv/Exceptions.py index 01e863f..53214df 100644 --- a/riscemu/priv/Exceptions.py +++ b/riscemu/priv/Exceptions.py @@ -7,6 +7,7 @@ import typing from .. import RiscemuBaseException from ..colors import FMT_PARSE, FMT_NONE +from ..types import UInt32 if typing.TYPE_CHECKING: from .ElfLoader import ElfInstruction @@ -29,7 +30,7 @@ class CpuTrap(BaseException): The isInterrupt bit in the mstatus register """ - mtval: int + mtval: UInt32 """ contents of the mtval register """ @@ -47,7 +48,7 @@ class CpuTrap(BaseException): 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.mtval = UInt32(mtval) self.priv = priv self.type = type diff --git a/riscemu/priv/ImageLoader.py b/riscemu/priv/ImageLoader.py index 9ef86e6..11f8fe7 100644 --- a/riscemu/priv/ImageLoader.py +++ b/riscemu/priv/ImageLoader.py @@ -26,7 +26,7 @@ class MemoryImageLoader(ProgramLoader): return argv, {} def parse(self) -> Iterable[Program]: - if self.options.get('debug', False): + if 'debug' not in self.options: yield self.parse_no_debug() return @@ -43,11 +43,11 @@ class MemoryImageLoader(ProgramLoader): if program.base is None: program.base = start - in_code_sec = get_section_base_name(sec_name) in INSTRUCTION_SECTION_NAMES + #in_code_sec = get_section_base_name(sec_name) in INSTRUCTION_SECTION_NAMES program.add_section( ElfMemorySection( data[start:start+size], sec_name, program.context, - name, start, MemoryFlags(in_code_sec, in_code_sec) + name, start, MemoryFlags(False, True) ) ) @@ -64,12 +64,12 @@ class MemoryImageLoader(ProgramLoader): p = Program(self.filename) p.add_section(ElfMemorySection( - bytearray(data), 'memory image contents', p.context, p.name, 0, MemoryFlags(False, True) + bytearray(data), '.text', p.context, p.name, 0, MemoryFlags(False, True) )) return p @classmethod def instantiate(cls, source_path: str, options: T_ParserOpts) -> 'ProgramLoader': - if os.path.exists(source_path + '.dbg'): + if os.path.isfile(source_path + '.dbg'): return MemoryImageLoader(source_path, dict(**options, debug=source_path + '.dbg')) return MemoryImageLoader(source_path, options) diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 483300e..c74d766 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -14,8 +14,9 @@ from .ImageLoader import MemoryImageLoader from .PrivMMU import PrivMMU from .PrivRV32I import PrivRV32I from .privmodes import PrivModes +from ..IO.TextIO import TextIO from ..instructions import RV32A, RV32M -from ..types import Program +from ..types import Program, UInt32 if typing.TYPE_CHECKING: from riscemu.instructions.instruction_set import InstructionSet @@ -55,12 +56,16 @@ class PrivCPU(CPU): self.exit_code = 0 self._time_start = 0 - self._time_timecmp = 0 + self._time_timecmp = UInt32(0) self._time_interrupt_enabled = False # performance counters self._perf_counters = list() + # add TextIO + io = TextIO(0xFF0000, 64) + self.mmu.load_section(io, True) + # init csr self._init_csr() @@ -105,11 +110,11 @@ class PrivCPU(CPU): def _init_csr(self): # set up CSR self.csr = CSR() - self.csr.set('mhartid', 0) # core id + self.csr.set('mhartid', UInt32(0)) # core id # TODO: set correct value - self.csr.set('mimpid', 0) # implementation id + self.csr.set('mimpid', UInt32(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 + self.csr.set('misa', UInt32((1 << 30) + (1 << 8) + (1 << 12))) # available ISA # CSR write callbacks: @@ -137,11 +142,11 @@ class PrivCPU(CPU): @self.csr.virtual_register('time') def get_time(): - return (time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start) & (2 ** 32 - 1) + return UInt32(time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start) @self.csr.virtual_register('timeh') def get_timeh(): - return (time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start) >> 32 + return UInt32((time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start) >> 32) # add minstret and mcycle counters @@ -156,7 +161,7 @@ class PrivCPU(CPU): self._timer_step() self._check_interrupt() ins = self.mmu.read_ins(self.pc) - if verbose and self.mode == PrivModes.USER: + if verbose and (self.mode == PrivModes.USER or self.conf.verbosity > 4): print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins)) self.run_instruction(ins) self.pc += self.INS_XLEN @@ -168,6 +173,7 @@ class PrivCPU(CPU): self.mmu.translate_address(self.pc), self.pc ) + FMT_NONE) + breakpoint() if self.conf.debug_on_exception: raise LaunchDebuggerException() self.pc += self.INS_XLEN @@ -197,16 +203,16 @@ class PrivCPU(CPU): 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_mstatus('mie', UInt32(0)) self.csr.set('mcause', trap.mcause) self.csr.set('mepc', self.pc - self.INS_XLEN) self.csr.set('mtval', trap.mtval) self.mode = trap.priv mtvec = self.csr.get('mtvec') if mtvec & 0b11 == 0: - self.pc = mtvec + self.pc = mtvec.value if mtvec & 0b11 == 1: - self.pc = (mtvec & 0b11111111111111111111111111111100) + (trap.code * 4) + self.pc = ((mtvec & 0b11111111111111111111111111111100) + (trap.code * 4)).value self.record_perf_profile() if len(self._perf_counters) > 100: self.show_perf() diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index 81f446a..409f2ef 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -44,7 +44,6 @@ class PrivRV32I(RV32I): old_val = self.cpu.csr.get(csr_addr) self.regs.set(rd, old_val) - def instruction_csrrc(self, ins: 'Instruction'): INS_NOT_IMPLEMENTED(ins) @@ -61,7 +60,6 @@ class PrivRV32I(RV32I): self.cpu.csr.assert_can_write(self.cpu.mode, addr) self.cpu.csr.set(addr, imm) - def instruction_csrrci(self, ins: 'Instruction'): INS_NOT_IMPLEMENTED(ins) @@ -77,10 +75,10 @@ class PrivRV32I(RV32I): self.cpu.mode = PrivModes(mpp) # restore pc mepc = self.cpu.csr.get('mepc') - self.cpu.pc = mepc - self.cpu.INS_XLEN + self.cpu.pc = (mepc - self.cpu.INS_XLEN).value if self.cpu.conf.verbosity > 0: - sec = self.mmu.get_sec_containing(mepc) + sec = self.mmu.get_sec_containing(mepc.value) if sec is not None: print(FMT_CPU + "[CPU] returning to mode {} in {} (0x{:x})".format( PrivModes(mpp).name, @@ -105,32 +103,32 @@ class PrivRV32I(RV32I): def instruction_beq(self, ins: 'Instruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 == rs2: - self.pc += dst - 4 + self.pc += dst.value - 4 def instruction_bne(self, ins: 'Instruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 != rs2: - self.pc += dst - 4 + self.pc += dst.value - 4 def instruction_blt(self, ins: 'Instruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 < rs2: - self.pc += dst - 4 + self.pc += dst.value - 4 def instruction_bge(self, ins: 'Instruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 >= rs2: - self.pc += dst - 4 + self.pc += dst.value - 4 def instruction_bltu(self, ins: 'Instruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) if rs1 < rs2: - self.pc += dst - 4 + self.pc += dst.value - 4 def instruction_bgeu(self, ins: 'Instruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) if rs1 >= rs2: - self.pc += dst - 4 + self.pc += dst.value - 4 # technically deprecated def instruction_j(self, ins: 'Instruction'): @@ -140,19 +138,24 @@ class PrivRV32I(RV32I): ASSERT_LEN(ins.args, 2) reg = ins.get_reg(0) addr = ins.get_imm(1) - if reg == 'ra' and self.cpu.mode == PrivModes.USER and self.cpu.conf.verbosity > 1: - print(FMT_CPU + 'Jumping to {} (0x{:x})'.format( + if reg == 'ra' and ( + (self.cpu.mode == PrivModes.USER and self.cpu.conf.verbosity > 1) or + (self.cpu.conf.verbosity > 3) + ): + print(FMT_CPU + 'Jumping from 0x{:x} to {} (0x{:x})'.format( + self.pc, self.mmu.translate_address(self.pc + addr), self.pc + addr ) + FMT_NONE) - self.regs.set(reg, self.pc) + self.regs.dump_reg_a() + self.regs.set(reg, Int32(self.pc)) self.pc += addr - 4 def instruction_jalr(self, ins: 'Instruction'): ASSERT_LEN(ins.args, 3) rd, rs, imm = self.parse_rd_rs_imm(ins) - self.regs.set(rd, self.pc) - self.pc = rs + imm - 4 + self.regs.set(rd, Int32(self.pc)) + self.pc = rs.value + imm.value - 4 def instruction_sbreak(self, ins: 'Instruction'): raise LaunchDebuggerException() diff --git a/riscemu/priv/__main__.py b/riscemu/priv/__main__.py index bbdd1fb..6e74029 100644 --- a/riscemu/priv/__main__.py +++ b/riscemu/priv/__main__.py @@ -34,4 +34,4 @@ if __name__ == '__main__': for program in program_iter: cpu.load_program(program) - cpu.launch() + cpu.launch(verbose=args.verbose > 4) diff --git a/riscemu/priv/types.py b/riscemu/priv/types.py index 585f580..f42d030 100644 --- a/riscemu/priv/types.py +++ b/riscemu/priv/types.py @@ -43,8 +43,8 @@ class ElfMemorySection(BinaryDataMemorySection): def __init__(self, data: bytearray, name: str, context: InstructionContext, owner: str, base: int, flags: MemoryFlags): super().__init__(data, name, context, owner, base=base, flags=flags) + self.read_ins = lru_cache(maxsize=self.size // 4)(self.read_ins) - @lru_cache def read_ins(self, offset): if not self.flags.executable: print(FMT_PARSE + "Reading instruction from non-executable memory!" + FMT_NONE) @@ -65,7 +65,7 @@ class ElfMemorySection(BinaryDataMemorySection): class MemoryImageDebugInfos: - VERSION = '1' + VERSION = '1.0.0' """ Schema version """ @@ -99,6 +99,8 @@ class MemoryImageDebugInfos: self.sections = sections self.symbols = symbols self.globals = globals + for name in globals: + globals[name] = set(globals[name]) self.base = base def serialize(self) -> str: @@ -110,7 +112,13 @@ class MemoryImageDebugInfos: return "<>".format(getattr(obj, '__qualname__', '{unknown}')) return json.dumps( - dict(sections=self.sections, symbols=self.symbols, globals=self.globals, base=self.base), + dict( + sections=self.sections, + symbols=self.symbols, + globals=self.globals, + base=self.base, + VERSION=self.VERSION + ), default=serialize ) @@ -124,7 +132,7 @@ class MemoryImageDebugInfos: version: str = json_obj.pop('VERSION') # compare major version - if version != cls.VERSION or version.split('.')[0] != cls.VERSION.split('.')[0]: + if version != cls.VERSION and version.split('.')[0] != cls.VERSION.split('.')[0]: raise RuntimeError( "Unknown MemoryImageDebugInfo version! This emulator expects version {}, debug info version {}".format( cls.VERSION, version diff --git a/riscemu/registers.py b/riscemu/registers.py index aa45915..caa7e36 100644 --- a/riscemu/registers.py +++ b/riscemu/registers.py @@ -8,6 +8,9 @@ from collections import defaultdict from .helpers import * +if typing.TYPE_CHECKING: + from .types import Int32 + class Registers: """ @@ -15,7 +18,8 @@ class Registers: """ def __init__(self): - self.vals = defaultdict(lambda: 0) + from .types import Int32 + self.vals = defaultdict(lambda: Int32(0)) self.last_set = None self.last_read = None @@ -81,7 +85,7 @@ class Registers: return FMT_GRAY + txt + FMT_NONE return txt - def set(self, reg, val, mark_set=True) -> bool: + def set(self, reg, val: 'Int32', mark_set=True) -> bool: """ Set a register content to val :param reg: The register to set @@ -89,6 +93,12 @@ class Registers: :param mark_set: If True, marks this register as "last accessed" (only used internally) :return: If the operation was successful """ + + from .types import Int32 + # remove after refactoring is complete + if not isinstance(val, Int32): + raise RuntimeError("Setting register to non-Int32 value! Please refactor your code!") + if reg == 'zero': return False # if reg not in Registers.all_registers(): @@ -99,10 +109,10 @@ class Registers: if mark_set: self.last_set = reg # check 32 bit signed bounds - self.vals[reg] = bind_twos_complement(val) + self.vals[reg] = val return True - def get(self, reg, mark_read=True): + def get(self, reg, mark_read=True) -> 'Int32': """ Retuns the contents of register reg :param reg: The register name diff --git a/riscemu/types.py b/riscemu/types.py index 0746d3d..4a4692f 100644 --- a/riscemu/types.py +++ b/riscemu/types.py @@ -12,11 +12,12 @@ import re import typing from abc import ABC, abstractmethod from collections import defaultdict +from ctypes import c_uint32, c_int32 from dataclasses import dataclass from typing import Dict, List, Optional, Tuple, Set, Union, Iterator, Callable, Type -from .config import RunConfig from .colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE, FMT_RED, FMT_BOLD +from .config import RunConfig from .exceptions import ParseException from .helpers import format_bytes, get_section_base_name from .registers import Registers @@ -35,6 +36,206 @@ T_ParserOpts = Dict[str, any] NUMBER_SYMBOL_PATTERN = re.compile(r'^\d+[fb]$') +class Int32: + _type = c_int32 + __slots__ = ('_val',) + + def __init__(self, val: Union[int, c_int32, c_uint32, 'Int32', bytes, bytearray] = 0): + if isinstance(val, (bytes, bytearray)): + self._val = self.__class__._type(int.from_bytes(val, 'little', signed=True)) + elif isinstance(val, self.__class__._type): + self._val = val + elif isinstance(val, (c_uint32, c_int32, Int32)): + self._val = self.__class__._type(val.value) + elif isinstance(val, int): + self._val = self.__class__._type(val) + else: + raise RuntimeError( + "Unknonw {} input type: {} ({})".format(self.__class__.__name__, type(val), val) + ) + + def __add__(self, other: Union['Int32', int]): + if isinstance(other, Int32): + other = other.value + + return self.__class__(self._val.value + other) + + def __sub__(self, other: Union['Int32', int]): + if isinstance(other, Int32): + other = other.value + return self.__class__(self._val.value - other) + + def __mul__(self, other: Union['Int32', int]): + if isinstance(other, Int32): + other = other.value + return self.__class__(self._val.value * other) + + def __truediv__(self, other): + return self // other + + def __floordiv__(self, other): + if isinstance(other, Int32): + other = other.value + return self.__class__(self.value // other) + + def __mod__(self, other: Union['Int32', int]): + if isinstance(other, Int32): + other = other.value + return self.__class__(self._val.value % other) + + def __and__(self, other: Union['Int32', int]): + if isinstance(other, Int32): + other = other.value + return self.__class__(self._val.value & other) + + def __or__(self, other: Union['Int32', int]): + if isinstance(other, Int32): + other = other.value + return self.__class__(self._val.value | other) + + def __xor__(self, other: Union['Int32', int]): + if isinstance(other, Int32): + other = other.value + return self.__class__(self._val.value ^ other) + + def __lshift__(self, other: Union['Int32', int]): + if isinstance(other, Int32): + other = other.value + return self.__class__(self.value << other) + + def __rshift__(self, other: Union['Int32', int]): + if isinstance(other, Int32): + other = other.value + return self.__class__(self.value >> other) + + def __eq__(self, other: Union['Int32', int]): + if isinstance(other, Int32): + other = other.value + return self.value == other + + def __neg__(self): + return self.__class__(-self._val.value) + + def __abs__(self): + return self.__class__(abs(self.value)) + + def __bytes__(self): + return self.to_bytes(4) + + def __repr__(self): + return '{}({})'.format(self.__class__.__name__, self.value) + + def __str__(self): + return str(self.value) + + def __format__(self, format_spec): + return self.value.__format__(format_spec) + + def __hash__(self): + return hash(self.value) + + def __gt__(self, other): + if isinstance(other, Int32): + other = other.value + return self.value > other + + def __lt__(self, other): + if isinstance(other, Int32): + other = other.value + return self.value < other + + def __le__(self, other): + if isinstance(other, Int32): + other = other.value + return self.value <= other + + def __ge__(self, other): + if isinstance(other, Int32): + other = other.value + return self.value >= other + + def __bool__(self): + return bool(self.value) + + def __cmp__(self, other): + if isinstance(other, Int32): + other = other.value + return self.value.__cmp__(other) + + # right handed binary operators + + def __radd__(self, other): + return self + other + + def __rsub__(self, other): + return self.__class__(other) - self + + def __rmul__(self, other): + return self * other + + def __rtruediv__(self, other): + return self.__class__(other) // self + + def __rfloordiv__(self, other): + return self.__class__(other) // self + + def __rmod__(self, other): + return self.__class__(other) % self + + def __rand__(self, other): + return self.__class__(other) & self + + def __ror__(self, other): + return self.__class__(other) | self + + def __rxor__(self, other): + return self.__class__(other) ^ self + + @property + def value(self): + return self._val.value + + def unsigned(self) -> 'UInt32': + return UInt32(self) + + def to_bytes(self, bytes: int = 4) -> bytearray: + return bytearray(self.unsigned_value.to_bytes(bytes, 'little')) + + def signed(self) -> 'Int32': + if self.__class__ == Int32: + return self + return Int32(self) + + @property + def unsigned_value(self): + return c_uint32(self.value).value + + def shift_right_logical(self, ammount: Union['Int32', int]): + if isinstance(ammount, Int32): + ammount = ammount.value + return self.__class__((self.value % 0x100000000) >> ammount) + + def __int__(self): + return self.value + + def __hex__(self): + return hex(self.value) + + +class UInt32(Int32): + _type = c_uint32 + + def unsigned(self) -> 'UInt32': + return self + + @property + def unsigned_value(self): + return self._val.value + + def shift_right_logical(self, ammount: Union['Int32', int]): + return self >> ammount + + @dataclass(frozen=True) class MemoryFlags: read_only: bool @@ -242,16 +443,17 @@ class Program: # print a warning when a section is located before the programs base if self.base is not None: if sec.base < self.base: - print(FMT_RED + FMT_BOLD + "WARNING: memory section {} in {} is placed before program base (0x{:x})".format( - sec, self.name, self.base - ) + FMT_NONE) + print( + FMT_RED + FMT_BOLD + "WARNING: memory section {} in {} is placed before program base (0x{:x})".format( + sec, self.name, self.base + ) + FMT_NONE) self.sections.append(sec) # keep section list ordered self.sections.sort(key=lambda section: section.base) def __repr__(self): - return "{}(name={},globals={},sections={},base={})".format( + return "{}(name={},sections={},base={})".format( self.__class__.__name__, self.name, self.global_labels, [s.name for s in self.sections], self.base ) @@ -273,6 +475,8 @@ class Program: This will do a small sanity check to prevent programs loading twice, or at addresses they don't expect to be loaded. + Then it will finalize all relative symbols defined in it to point to the correct addresses. + :param at_addr: the address where the program will be located """ if self.is_loaded: @@ -449,4 +653,4 @@ class CPU(ABC): @property def programs(self): - return self.mmu.programs \ No newline at end of file + return self.mmu.programs diff --git a/setup.py b/setup.py index 238e88f..edaae98 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ with open("README.md", "r", encoding="utf-8") as fh: setuptools.setup( name="riscemu", version=riscemu.__version__, - author="Anton Lydike", + author=riscemu.__author__, author_email="pip@antonlydike.de", description="RISC-V userspace and privileged emulator", long_description=long_description, diff --git a/test/__init__.py b/test/__init__.py index 8030002..1f2c0dd 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,2 +1,3 @@ from .test_tokenizer import * -from .test_helpers import * \ No newline at end of file +from .test_helpers import * +from .test_integers import * \ No newline at end of file diff --git a/test/test_helpers.py b/test/test_helpers.py index 60d93b0..f37517d 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -4,29 +4,6 @@ from riscemu.helpers import * class TestHelpers(TestCase): - def test_int_to_bytes(self): - self.assertEqual(int_to_bytes(-1), bytearray([0xff] * 4), "-1") - self.assertEqual(int_to_bytes(1), bytearray([0, 0, 0, 1]), "1") - self.assertEqual(int_to_bytes(1231132), bytearray(b'\x00\x12\xc9\x1c'), "random number") - self.assertEqual(int_to_bytes(-1231132), bytearray(b'\xff\xed6\xe4'), "random negative number") - - def test_int_from_bytes(self): - self.assertEqual(bytearray([0xff] * 4), int_to_bytes(-1), "-1") - self.assertEqual(bytearray([0, 0, 0, 1]), int_to_bytes(1), "1") - self.assertEqual(bytearray(b'\x00\x12\xc9\x1c'), int_to_bytes(1231132), "random number") - self.assertEqual(bytearray(b'\xff\xed6\xe4'), int_to_bytes(-1231132), "random negative number") - - def test_to_unsigned(self): - self.assertEqual(to_unsigned(-1), 0xFFFFFFFF) - self.assertEqual(to_unsigned(-100), 0xffffff9c) - self.assertEqual(to_unsigned(1), 1) - self.assertEqual(to_unsigned(0xffffffff), 0xffffffff) - self.assertEqual(to_unsigned(0xffed36e4), 0xffed36e4) - - def test_to_signed(self): - self.assertEqual(to_signed(0xFFFFFFFF), -1) - self.assertEqual(to_signed(0xffed36e4), -1231132) - self.assertEqual(to_signed(0x0FFFFFFF), 0x0FFFFFFF) def test_bind_twos_complement(self): minval = -(1 << 31) diff --git a/test/test_integers.py b/test/test_integers.py new file mode 100644 index 0000000..bb11141 --- /dev/null +++ b/test/test_integers.py @@ -0,0 +1,19 @@ +from unittest import TestCase + +from riscemu.types import Int32, UInt32 + + +class TestTokenizer(TestCase): + + def test_logical_right_shift(self): + a = Int32(100) + self.assertEqual(a.shift_right_logical(0), a) + self.assertEqual(a.shift_right_logical(10), 0) + self.assertEqual(a.shift_right_logical(1), 100>>1) + + a = Int32(-100) + self.assertEqual(a.shift_right_logical(0), a) + self.assertEqual(a.shift_right_logical(1), 2147483598) + self.assertEqual(a.shift_right_logical(10), 4194303) + self.assertEqual(a.shift_right_logical(31), 1) + self.assertEqual(a.shift_right_logical(32), 0) diff --git a/test/test_isa.py b/test/test_isa.py index 80a7a13..cc69052 100644 --- a/test/test_isa.py +++ b/test/test_isa.py @@ -1,6 +1,4 @@ from riscemu.colors import FMT_ERROR, FMT_NONE, FMT_BOLD, FMT_GREEN -from riscemu.exceptions import ASSERT_LEN -from riscemu.helpers import int_from_bytes from riscemu.instructions import InstructionSet from riscemu.types import Instruction, CPU from riscemu.decoder import RISCV_REGS