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
assembly-parser-rework
Anton Lydike 3 years ago
parent cd5795bb74
commit 71093fe72f

@ -4,6 +4,9 @@
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/venv" />
<excludeFolder url="file://$MODULE_DIR$/dist" />
<excludeFolder url="file://$MODULE_DIR$/.mypy_cache" />
<excludeFolder url="file://$MODULE_DIR$/riscemu.egg-info" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />

@ -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

@ -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
)

@ -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))

@ -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))

@ -25,4 +25,4 @@ from .parser import tokenize, parse_tokens, AssemblyFileLoader
__author__ = "Anton Lydike <Anton@Lydike.com>"
__copyright__ = "Copyright 2021 Anton Lydike"
__version__ = '1.0.0'
__version__ = '2.0.0a1'

@ -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

@ -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]

@ -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)

@ -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)

@ -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)

@ -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 <name> 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 <name> 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 <name> 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
"""

@ -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

@ -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)

@ -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

@ -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)

@ -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()

@ -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()

@ -34,4 +34,4 @@ if __name__ == '__main__':
for program in program_iter:
cpu.load_program(program)
cpu.launch()
cpu.launch(verbose=args.verbose > 4)

@ -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 "<<unserializable {}>>".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

@ -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

@ -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
return self.mmu.programs

@ -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,

@ -1,2 +1,3 @@
from .test_tokenizer import *
from .test_helpers import *
from .test_helpers import *
from .test_integers import *

@ -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)

@ -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)

@ -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

Loading…
Cancel
Save