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
This commit is contained in:
parent
cd5795bb74
commit
71093fe72f
3
.idea/riscemu.iml
generated
3
.idea/riscemu.iml
generated
@ -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
|
||||
|
||||
@abstractmethod
|
||||
def read(self, addr: int, size: int):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def write(self, addr: int, data: bytearray, size: int):
|
||||
pass
|
||||
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)
|
||||
|
||||
def contains(self, addr, size: int = 0):
|
||||
return self.addr <= addr < self.addr + self.size and \
|
||||
self.addr <= addr + size <= self.addr + self.size
|
||||
return self.base <= addr < self.base + self.size and \
|
||||
self.base <= addr + size <= self.base + self.size
|
||||
|
||||
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 __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 .colors import FMT_PARSE, FMT_NONE
|
||||
from .exceptions import ParseException, ASSERT_LEN, ASSERT_NOT_NULL
|
||||
from .tokenizer import Token
|
||||
from .base import BinaryDataMemorySection, InstructionMemorySection
|
||||
from .colors import FMT_PARSE, FMT_NONE
|
||||
from .exceptions import ParseException, ASSERT_LEN
|
||||
from .helpers import parse_numeric_argument, align_addr, get_section_base_name
|
||||
from .tokenizer import Token
|
||||
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
|
||||
|
216
riscemu/types.py
216
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
|
||||
return self.mmu.programs
|
||||
|
2
setup.py
2
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,
|
||||
|
@ -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)
|
||||
|
19
test/test_integers.py
Normal file
19
test/test_integers.py
Normal file
@ -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…
Reference in New Issue
Block a user