diff --git a/riscemu/CPU.py b/riscemu/CPU.py index 25539c3..dc4d304 100644 --- a/riscemu/CPU.py +++ b/riscemu/CPU.py @@ -30,7 +30,9 @@ class UserModeCPU(CPU): It is initialized with a configuration and a list of instruction sets. """ - def __init__(self, instruction_sets: List[Type['riscemu.InstructionSet']], conf: RunConfig): + def __init__( + self, instruction_sets: List[Type["riscemu.InstructionSet"]], conf: RunConfig + ): """ Creates a CPU instance. @@ -54,7 +56,11 @@ class UserModeCPU(CPU): Execute a single instruction, then return. """ if self.halted: - print(FMT_CPU + "[CPU] Program exited with code {}".format(self.exit_code) + FMT_NONE) + print( + FMT_CPU + + "[CPU] Program exited with code {}".format(self.exit_code) + + FMT_NONE + ) return launch_debugger = False @@ -63,7 +69,9 @@ class UserModeCPU(CPU): self.cycle += 1 ins = self.mmu.read_ins(self.pc) if verbose: - print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins)) + print( + FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins) + ) self.pc += self.INS_XLEN self.run_instruction(ins) except RiscemuBaseException as ex: @@ -72,12 +80,12 @@ class UserModeCPU(CPU): if self.debugger_active: raise ex - print(FMT_CPU + '[CPU] Debugger launch requested!' + FMT_NONE) + print(FMT_CPU + "[CPU] Debugger launch requested!" + FMT_NONE) launch_debugger = True else: print(ex.message()) ex.print_stacktrace() - print(FMT_CPU + '[CPU] Halting due to exception!' + FMT_NONE) + print(FMT_CPU + "[CPU] Halting due to exception!" + FMT_NONE) self.halted = True if launch_debugger: @@ -88,7 +96,11 @@ class UserModeCPU(CPU): self.step(verbose) if self.conf.verbosity > 0: - print(FMT_CPU + "[CPU] Program exited with code {}".format(self.exit_code) + FMT_NONE) + print( + FMT_CPU + + "[CPU] Program exited with code {}".format(self.exit_code) + + FMT_NONE + ) def setup_stack(self, stack_size=1024 * 4) -> bool: """ @@ -98,22 +110,26 @@ class UserModeCPU(CPU): """ stack_sec = BinaryDataMemorySection( bytearray(stack_size), - '.stack', + ".stack", None, # FIXME: why does a binary data memory section require a context? - '', - 0 + "", + 0, ) if not self.mmu.load_section(stack_sec, fixed_position=False): print(FMT_ERROR + "[CPU] Could not insert stack section!" + FMT_NONE) return False - self.regs.set('sp', Int32(stack_sec.base + stack_sec.size)) + self.regs.set("sp", Int32(stack_sec.base + stack_sec.size)) if self.conf.verbosity > 1: - print(FMT_CPU + "[CPU] Created stack of size {} at 0x{:x}".format( - stack_size, stack_sec.base - ) + FMT_NONE) + print( + FMT_CPU + + "[CPU] Created stack of size {} at 0x{:x}".format( + stack_size, stack_sec.base + ) + + FMT_NONE + ) return True diff --git a/riscemu/IO/IOModule.py b/riscemu/IO/IOModule.py index ade42c5..3af7f7a 100644 --- a/riscemu/IO/IOModule.py +++ b/riscemu/IO/IOModule.py @@ -5,12 +5,21 @@ from riscemu.types import MemorySection, MemoryFlags, T_RelativeAddress class IOModule(MemorySection, ABC): - def __init__(self, name: str, flags: MemoryFlags, size: int, owner: str = 'system', base: int = 0): + 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.base <= addr < self.base + self.size and \ - self.base <= addr + size <= self.base + self.size + return ( + self.base <= addr < self.base + self.size + and self.base <= addr + size <= self.base + self.size + ) def dump(self, *args, **kwargs): print(self) diff --git a/riscemu/IO/TextIO.py b/riscemu/IO/TextIO.py index a57e1ab..30d790d 100644 --- a/riscemu/IO/TextIO.py +++ b/riscemu/IO/TextIO.py @@ -8,7 +8,9 @@ class TextIO(IOModule): 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) + super(TextIO, self).__init__( + "TextIO", MemoryFlags(False, False), buflen + 4, base=base + ) self.buff = bytearray(buflen) self.current_line = "" @@ -23,15 +25,15 @@ class TextIO(IOModule): self._print() return buff_start = addr - 4 - self.buff[buff_start:buff_start + size] = data[0:size] + self.buff[buff_start : buff_start + size] = data[0:size] def _print(self): buff = self.buff self.buff = bytearray(self.size) - if b'\x00' in buff: - buff = buff.split(b'\x00')[0] - text = buff.decode('ascii') - if '\n' in text: + if b"\x00" in buff: + buff = buff.split(b"\x00")[0] + text = buff.decode("ascii") + if "\n" in text: lines = text.split("\n") lines[0] = self.current_line + lines[0] for line in lines[:-1]: diff --git a/riscemu/MMU.py b/riscemu/MMU.py index 1799436..a13cb0c 100644 --- a/riscemu/MMU.py +++ b/riscemu/MMU.py @@ -8,8 +8,15 @@ from typing import Dict, List, Optional, Union from .colors import * from .helpers import align_addr -from .types import Instruction, MemorySection, MemoryFlags, T_AbsoluteAddress, \ - Program, InstructionContext, Int32 +from .types import ( + Instruction, + MemorySection, + MemoryFlags, + T_AbsoluteAddress, + Program, + InstructionContext, + Int32, +) from .types.exceptions import InvalidAllocationException, MemoryAccessException @@ -80,8 +87,14 @@ class MMU: """ sec = self.get_sec_containing(addr) if sec is None: - print(FMT_MEM + "[MMU] Trying to read instruction form invalid region! (read at {}) ".format(addr) - + "Have you forgotten an exit syscall or ret statement?" + FMT_NONE) + print( + FMT_MEM + + "[MMU] Trying to read instruction form invalid region! (read at {}) ".format( + addr + ) + + "Have you forgotten an exit syscall or ret statement?" + + FMT_NONE + ) raise RuntimeError("No next instruction available!") return sec.read_ins(addr - sec.base) @@ -97,8 +110,16 @@ class MMU: 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') + 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: bytearray): @@ -111,8 +132,16 @@ class MMU: """ sec = self.get_sec_containing(addr) if sec is None: - print(FMT_MEM + '[MMU] Invalid write into non-initialized region at 0x{:08X}'.format(addr) + FMT_NONE) - raise MemoryAccessException("region is non-initialized!", addr, size, 'write') + print( + FMT_MEM + + "[MMU] Invalid write into non-initialized region at 0x{:08X}".format( + addr + ) + + FMT_NONE + ) + raise MemoryAccessException( + "region is non-initialized!", addr, size, "write" + ) return sec.write(addr - sec.base, size, data) @@ -126,7 +155,11 @@ class MMU: """ sec = self.get_sec_containing(addr) if sec is None: - print(FMT_MEM + "[MMU] No section containing addr 0x{:08X}".format(addr) + FMT_NONE) + print( + FMT_MEM + + "[MMU] No section containing addr 0x{:08X}".format(addr) + + FMT_NONE + ) return sec.dump(addr - sec.base, *args, **kwargs) @@ -138,10 +171,18 @@ 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])) + print( + " Found global symbol {}: 0x{:X}".format( + symb, self.global_symbols[symb] + ) + ) 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)) + print( + " Found local labels {}: 0x{:X} in {}".format( + symb, bin.context.labels[symb], bin.name + ) + ) def read_int(self, addr: int) -> Int32: return Int32(self.read(addr, 4)) @@ -154,17 +195,27 @@ class MMU: bin = self.get_bin_containing(address) secs = set(sec.name for sec in bin.sections) if bin else [] elf_markers = { - '__global_pointer$', '_fdata', '_etext', '_gp', - '_bss_start', '_bss_end', '_ftext', '_edata', '_end', '_fbss' + "__global_pointer$", + "_fdata", + "_etext", + "_gp", + "_bss_start", + "_bss_end", + "_ftext", + "_edata", + "_end", + "_fbss", } def key(x): name, val = x return address - val - best_fit = sorted(filter(lambda x: x[1] <= address, sec.context.labels.items()), key=key) + best_fit = sorted( + filter(lambda x: x[1] <= address, sec.context.labels.items()), key=key + ) - best = ('', float('inf')) + best = ("", float("inf")) for name, val in best_fit: if address - val < best[1]: best = (name, val) @@ -178,13 +229,14 @@ class MMU: if not name: return "{}:{} + 0x{:x} (0x{:x})".format( - sec.owner, sec.name, - address - sec.base, address + sec.owner, sec.name, address - sec.base, address ) - return str('{}:{} at {} (0x{:0x}) + 0x{:0x}'.format( - sec.owner, sec.name, name, val, address - val - )) + 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 @@ -210,13 +262,24 @@ class MMU: def load_program(self, program: Program, align_to: int = 4): if program.base is not None: - if not self.has_continous_free_region(program.base, program.base + program.size): - print(FMT_MEM + "Cannot load program {} into desired space (0x{:0x}-0x{:0x}), area occupied.".format( - program.name, program.base, program.base + program.size - ) + FMT_NONE) - raise InvalidAllocationException("Area occupied".format( - program.name, program.base, program.base + program.size - ), program.name, program.size, MemoryFlags(False, True)) + if not self.has_continous_free_region( + program.base, program.base + program.size + ): + print( + FMT_MEM + + "Cannot load program {} into desired space (0x{:0x}-0x{:0x}), area occupied.".format( + program.name, program.base, program.base + program.size + ) + + FMT_NONE + ) + raise InvalidAllocationException( + "Area occupied".format( + program.name, program.base, program.base + program.size + ), + program.name, + program.size, + MemoryFlags(False, True), + ) at_addr = program.base else: @@ -244,7 +307,12 @@ class MMU: self.sections.append(sec) self._update_state() else: - print(FMT_MEM + '[MMU] Cannot place section {} at {}, space is occupied!'.format(sec, sec.base)) + print( + FMT_MEM + + "[MMU] Cannot place section {} at {}, space is occupied!".format( + sec, sec.base + ) + ) return False else: at_addr = align_addr(self.get_guaranteed_free_address(), 8) @@ -269,8 +337,7 @@ class MMU: def __repr__(self): return "{}(\n\t{}\n)".format( - self.__class__.__name__, - "\n\t".join(repr(x) for x in self.programs) + self.__class__.__name__, "\n\t".join(repr(x) for x in self.programs) ) def context_for(self, addr: T_AbsoluteAddress) -> InstructionContext: diff --git a/riscemu/__init__.py b/riscemu/__init__.py index c2904c0..13deeba 100644 --- a/riscemu/__init__.py +++ b/riscemu/__init__.py @@ -8,8 +8,17 @@ This package aims at providing an all-round usable RISC-V emulator and debugger It contains everything needed to run assembly files, so you don't need any custom compilers or toolchains """ -from .types.exceptions import RiscemuBaseException, LaunchDebuggerException, InvalidSyscallException, LinkerException, \ - ParseException, NumberFormatException, InvalidRegisterException, MemoryAccessException, OutOfMemoryException +from .types.exceptions import ( + RiscemuBaseException, + LaunchDebuggerException, + InvalidSyscallException, + LinkerException, + ParseException, + NumberFormatException, + InvalidRegisterException, + MemoryAccessException, + OutOfMemoryException, +) from .instructions import * @@ -25,4 +34,4 @@ from .parser import tokenize, parse_tokens, AssemblyFileLoader __author__ = "Anton Lydike " __copyright__ = "Copyright 2022 Anton Lydike" -__version__ = '2.0.5' +__version__ = "2.0.5" diff --git a/riscemu/__main__.py b/riscemu/__main__.py index 8c4f8a7..d2e5e86 100644 --- a/riscemu/__main__.py +++ b/riscemu/__main__.py @@ -8,7 +8,7 @@ This file holds the logic for starting the emulator from the CLI from riscemu import RiscemuBaseException, __copyright__, __version__ from riscemu.CPU import UserModeCPU -if __name__ == '__main__': +if __name__ == "__main__": from .config import RunConfig from .instructions import InstructionSetDict from .colors import FMT_BOLD, FMT_MAGENTA @@ -18,11 +18,12 @@ if __name__ == '__main__': all_ins_names = list(InstructionSetDict.keys()) - if '--version' in sys.argv: - print("riscemu version {}\n{}\n\nAvailable ISA: {}".format( - __version__, __copyright__, - ", ".join(InstructionSetDict.keys()) - )) + if "--version" in sys.argv: + print( + "riscemu version {}\n{}\n\nAvailable ISA: {}".format( + __version__, __copyright__, ", ".join(InstructionSetDict.keys()) + ) + ) sys.exit() class OptionStringAction(argparse.Action): @@ -49,56 +50,98 @@ if __name__ == '__main__': d = {} if not self.omit_empty: d.update(self.keys) - for x in values.split(','): + for x in values.split(","): if x in self.keys: d[x] = True else: - raise ValueError('Invalid parameter supplied: ' + x) + raise ValueError("Invalid parameter supplied: " + x) setattr(namespace, self.dest, d) + parser = argparse.ArgumentParser( + description="RISC-V Userspace parser and emulator", + prog="riscemu", + formatter_class=argparse.RawTextHelpFormatter, + ) + parser.add_argument( + "files", + metavar="file.asm", + type=str, + nargs="+", + help="The assembly files to load, the last one will be run", + ) - parser = argparse.ArgumentParser(description='RISC-V Userspace parser and emulator', prog='riscemu', - formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument('files', metavar='file.asm', type=str, nargs='+', - help='The assembly files to load, the last one will be run') - - parser.add_argument('--options', '-o', action=OptionStringAction, - keys=('disable_debug', 'no_syscall_symbols', 'fail_on_ex', 'add_accept_imm', 'unlimited_regs'), - help="""Toggle options. Available options are: + parser.add_argument( + "--options", + "-o", + action=OptionStringAction, + keys=( + "disable_debug", + "no_syscall_symbols", + "fail_on_ex", + "add_accept_imm", + "unlimited_regs", + ), + help="""Toggle options. Available options are: disable_debug: Disable ebreak instructions no_syscall_symbols: Don't add symbols for SCALL_EXIT and others fail_on_ex: If set, exceptions won't trigger the debugger add_accept_imm: Accept "add rd, rs, imm" instruction (instead of addi) -unlimited_regs: Allow an unlimited number of registers""") +unlimited_regs: Allow an unlimited number of registers""", + ) - parser.add_argument('--syscall-opts', '-so', action=OptionStringAction, - keys=('fs_access', 'disable_input')) + parser.add_argument( + "--syscall-opts", + "-so", + action=OptionStringAction, + keys=("fs_access", "disable_input"), + ) - parser.add_argument('--instruction-sets', '-is', action=OptionStringAction, - help="Instruction sets to load, available are: {}. All are enabled by default" - .format(", ".join(all_ins_names)), keys={k: True for k in all_ins_names}, omit_empty=True) + parser.add_argument( + "--instruction-sets", + "-is", + action=OptionStringAction, + help="Instruction sets to load, available are: {}. All are enabled by default".format( + ", ".join(all_ins_names) + ), + keys={k: True for k in all_ins_names}, + omit_empty=True, + ) - parser.add_argument('--stack_size', type=int, help='Stack size of loaded programs, defaults to 8MB', nargs='?') + parser.add_argument( + "--stack_size", + type=int, + help="Stack size of loaded programs, defaults to 8MB", + nargs="?", + ) - parser.add_argument('-v', '--verbose', help="Verbosity level (can be used multiple times)", action='count', - default=0) + parser.add_argument( + "-v", + "--verbose", + help="Verbosity level (can be used multiple times)", + action="count", + default=0, + ) - parser.add_argument('--interactive', help="Launch the interactive debugger instantly instead of loading any " - "programs", action='store_true') + parser.add_argument( + "--interactive", + help="Launch the interactive debugger instantly instead of loading any " + "programs", + action="store_true", + ) args = parser.parse_args() # create a RunConfig from the cli args cfg_dict = dict( stack_size=args.stack_size, - debug_instruction=not args.options['disable_debug'], - include_scall_symbols=not args.options['no_syscall_symbols'], - debug_on_exception=not args.options['fail_on_ex'], - add_accept_imm=args.options['add_accept_imm'], - unlimited_registers=args.options['unlimited_regs'], - scall_fs=args.syscall_opts['fs_access'], - scall_input=not args.syscall_opts['disable_input'], - verbosity=args.verbose + debug_instruction=not args.options["disable_debug"], + include_scall_symbols=not args.options["no_syscall_symbols"], + debug_on_exception=not args.options["fail_on_ex"], + add_accept_imm=args.options["add_accept_imm"], + unlimited_registers=args.options["unlimited_regs"], + scall_fs=args.syscall_opts["fs_access"], + scall_input=not args.syscall_opts["disable_input"], + verbosity=args.verbose, ) for k, v in dict(cfg_dict).items(): if v is None: @@ -106,15 +149,13 @@ unlimited_regs: Allow an unlimited number of registers""") cfg = RunConfig(**cfg_dict) - if not hasattr(args, 'ins'): - setattr(args, 'ins', {k: True for k in all_ins_names}) + if not hasattr(args, "ins"): + setattr(args, "ins", {k: True for k in all_ins_names}) FMT_PRINT = FMT_BOLD + FMT_MAGENTA # parse required instruction sets - ins_to_load = [ - InstructionSetDict[name] for name, b in args.ins.items() if b - ] + ins_to_load = [InstructionSetDict[name] for name, b in args.ins.items() if b] try: cpu = UserModeCPU(ins_to_load, cfg) diff --git a/riscemu/assembler.py b/riscemu/assembler.py index 1633377..b03e289 100644 --- a/riscemu/assembler.py +++ b/riscemu/assembler.py @@ -6,10 +6,17 @@ from .colors import FMT_PARSE, FMT_NONE from riscemu.types.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, BinaryDataMemorySection, \ - InstructionMemorySection, Int32 - -INSTRUCTION_SECTION_NAMES = ('.text', '.init', '.fini') +from .types import ( + Program, + T_RelativeAddress, + InstructionContext, + Instruction, + BinaryDataMemorySection, + InstructionMemorySection, + Int32, +) + +INSTRUCTION_SECTION_NAMES = (".text", ".init", ".fini") """ A tuple containing all section names which contain executable code (instead of data) @@ -47,8 +54,7 @@ class CurrentSection: def __repr__(self): return "{}(name={},data={},type={})".format( - self.__class__.__name__, self.name, - self.data, self.type.name + self.__class__.__name__, self.name, self.data, self.type.name ) @@ -72,12 +78,20 @@ class ParseContext: if self.section.type == MemorySectionType.Data: section = BinaryDataMemorySection( - self.section.data, self.section.name, self.context, self.program.name, self.section.base + self.section.data, + self.section.name, + self.context, + self.program.name, + self.section.base, ) self.program.add_section(section) elif self.section.type == MemorySectionType.Instructions: section = InstructionMemorySection( - self.section.data, self.section.name, self.context, self.program.name, self.section.base + self.section.data, + self.section.name, + self.context, + self.program.name, + self.section.base, ) self.program.add_section(section) @@ -89,7 +103,9 @@ class ParseContext: self._finalize_section() self.section = CurrentSection(name, type, base) - def add_label(self, name: str, value: int, is_global: bool = False, is_relative: bool = False): + def add_label( + self, name: str, value: int, is_global: bool = False, is_relative: bool = False + ): self.context.labels[name] = value if is_global: self.program.global_labels.add(name) @@ -109,10 +125,16 @@ class ParseContext: def ASSERT_IN_SECTION_TYPE(context: ParseContext, type: MemorySectionType): if context.section is None: - raise ParseException('Error, expected to be in {} section, but no section is present...'.format(type.name)) + raise ParseException( + "Error, expected to be in {} section, but no section is present...".format( + type.name + ) + ) if context.section.type != type: raise ParseException( - 'Error, expected to be in {} section, but currently in {}...'.format(type.name, context.section) + "Error, expected to be in {} section, but currently in {}...".format( + type.name, context.section + ) ) @@ -174,7 +196,9 @@ class AssemblerDirectives: cls.add_bytes(size, bytearray(size), context) @classmethod - def add_bytes(cls, size: int, content: Union[None, int, bytearray], context: ParseContext): + def add_bytes( + cls, size: int, content: Union[None, int, bytearray], context: ParseContext + ): ASSERT_IN_SECTION_TYPE(context, MemorySectionType.Data) if content is None: @@ -187,9 +211,9 @@ class AssemblerDirectives: @classmethod def add_text(cls, text: str, context: ParseContext, zero_terminate: bool = True): # replace '\t' and '\n' escape sequences - text = text.replace('\\n', '\n').replace('\\t', '\t') + text = text.replace("\\n", "\n").replace("\\t", "\t") - encoded_bytes = bytearray(text.encode('ascii')) + encoded_bytes = bytearray(text.encode("ascii")) if zero_terminate: encoded_bytes += bytearray(1) cls.add_bytes(len(encoded_bytes), encoded_bytes, context) @@ -197,24 +221,36 @@ class AssemblerDirectives: @classmethod def handle_instruction(cls, token: Token, args: Tuple[str], context: ParseContext): op = token.value[1:] - if hasattr(cls, 'op_' + op): - getattr(cls, 'op_' + op)(token, args, context) - elif op in ('text', 'data', 'rodata', 'bss', 'sbss'): + if hasattr(cls, "op_" + op): + getattr(cls, "op_" + op)(token, args, context) + elif op in ("text", "data", "rodata", "bss", "sbss"): cls.op_section(token, (token.value,), context) - elif op in ('string', 'asciiz', 'asciz', 'ascii'): + elif op in ("string", "asciiz", "asciz", "ascii"): ASSERT_LEN(args, 1) - cls.add_text(args[0], context, op == 'ascii') + cls.add_text(args[0], context, op == "ascii") elif op in DATA_OP_SIZES: size = DATA_OP_SIZES[op] for arg in args: cls.add_bytes(size, parse_numeric_argument(arg), context) else: - print(FMT_PARSE + "Unknown assembler directive: {} {} in {}".format(token, args, context) + FMT_NONE) + print( + FMT_PARSE + + "Unknown assembler directive: {} {} in {}".format( + token, args, context + ) + + FMT_NONE + ) DATA_OP_SIZES = { - 'byte': 1, - '2byte': 2, 'half': 2, 'short': 2, - '4byte': 4, 'word': 4, 'long': 4, - '8byte': 8, 'dword': 8, 'quad': 8, + "byte": 1, + "2byte": 2, + "half": 2, + "short": 2, + "4byte": 4, + "word": 4, + "long": 4, + "8byte": 8, + "dword": 8, + "quad": 8, } diff --git a/riscemu/colors.py b/riscemu/colors.py index c2a7182..c3759f8 100644 --- a/riscemu/colors.py +++ b/riscemu/colors.py @@ -6,18 +6,18 @@ SPDX-License-Identifier: MIT # Colors -FMT_RED = '\033[31m' -FMT_ORANGE = '\033[33m' -FMT_GRAY = '\033[37m' -FMT_CYAN = '\033[36m' -FMT_GREEN = '\033[32m' -FMT_MAGENTA = '\033[35m' -FMT_BLUE = '\033[34m' -FMT_YELLOW = '\033[93m' +FMT_RED = "\033[31m" +FMT_ORANGE = "\033[33m" +FMT_GRAY = "\033[37m" +FMT_CYAN = "\033[36m" +FMT_GREEN = "\033[32m" +FMT_MAGENTA = "\033[35m" +FMT_BLUE = "\033[34m" +FMT_YELLOW = "\033[93m" -FMT_BOLD = '\033[1m' -FMT_NONE = '\033[0m' -FMT_UNDERLINE = '\033[4m' +FMT_BOLD = "\033[1m" +FMT_NONE = "\033[0m" +FMT_UNDERLINE = "\033[4m" FMT_ERROR = FMT_RED + FMT_BOLD @@ -26,4 +26,4 @@ FMT_PARSE = FMT_CYAN + FMT_BOLD FMT_CPU = FMT_BLUE + FMT_BOLD FMT_SYSCALL = FMT_YELLOW + FMT_BOLD FMT_DEBUG = FMT_MAGENTA + FMT_BOLD -FMT_CSR = FMT_ORANGE + FMT_BOLD \ No newline at end of file +FMT_CSR = FMT_ORANGE + FMT_BOLD diff --git a/riscemu/debug.py b/riscemu/debug.py index 5e4a6f4..7d7a62f 100644 --- a/riscemu/debug.py +++ b/riscemu/debug.py @@ -11,10 +11,10 @@ from .helpers import * if typing.TYPE_CHECKING: from riscemu import CPU, Registers -HIST_FILE = os.path.join(os.path.expanduser('~'), '.riscemu_history') +HIST_FILE = os.path.join(os.path.expanduser("~"), ".riscemu_history") -def launch_debug_session(cpu: 'CPU', prompt=""): +def launch_debug_session(cpu: "CPU", prompt=""): if cpu.debugger_active: return import code @@ -39,7 +39,7 @@ def launch_debug_session(cpu: 'CPU', prompt=""): mmu.dump(what, *args, **kwargs) def dump_stack(*args, **kwargs): - mmu.dump(regs.get('sp'), *args, **kwargs) + mmu.dump(regs.get("sp"), *args, **kwargs) def ins(): print("Current instruction at 0x{:08X}:".format(cpu.pc)) @@ -51,11 +51,7 @@ def launch_debug_session(cpu: 'CPU', prompt=""): return context = mmu.context_for(cpu.pc) - ins = SimpleInstruction( - name, - tuple(args), - context, - cpu.pc) + ins = SimpleInstruction(name, tuple(args), context, cpu.pc) print(FMT_DEBUG + "Running instruction {}".format(ins) + FMT_NONE) cpu.run_instruction(ins) @@ -63,7 +59,7 @@ def launch_debug_session(cpu: 'CPU', prompt=""): try: cpu.run(verbose) except LaunchDebuggerException: - print(FMT_DEBUG + 'Returning to debugger...') + print(FMT_DEBUG + "Returning to debugger...") return def step(): @@ -92,4 +88,3 @@ def launch_debug_session(cpu: 'CPU', prompt=""): finally: cpu.debugger_active = False readline.write_history_file(HIST_FILE) - diff --git a/riscemu/decoder/__init__.py b/riscemu/decoder/__init__.py index bfb9282..1592508 100644 --- a/riscemu/decoder/__init__.py +++ b/riscemu/decoder/__init__.py @@ -1,2 +1,2 @@ from .decoder import decode, RISCV_REGS -from .formatter import format_ins \ No newline at end of file +from .formatter import format_ins diff --git a/riscemu/decoder/__main__.py b/riscemu/decoder/__main__.py index 98bc146..fec397a 100644 --- a/riscemu/decoder/__main__.py +++ b/riscemu/decoder/__main__.py @@ -1,4 +1,4 @@ -if __name__ == '__main__': +if __name__ == "__main__": import code import readline import rlcompleter @@ -14,4 +14,6 @@ if __name__ == '__main__': readline.set_completer(rlcompleter.Completer(sess_vars).complete) readline.set_completer(rlcompleter.Completer(sess_vars).complete) readline.parse_and_bind("tab: complete") - code.InteractiveConsole(sess_vars).interact(banner="Interaktive decoding session started...", exitmsg="Closing...") + code.InteractiveConsole(sess_vars).interact( + banner="Interaktive decoding session started...", exitmsg="Closing..." + ) diff --git a/riscemu/decoder/decoder.py b/riscemu/decoder/decoder.py index 4bdaed8..3a93191 100644 --- a/riscemu/decoder/decoder.py +++ b/riscemu/decoder/decoder.py @@ -5,13 +5,14 @@ from typing import Tuple, List def print_ins(ins: int): print(" f7 rs2 rs1 f3 rd op") print( - f"0b{ins >> 25 :07b}_{(ins >> 20) & 0b11111:05b}_{(ins >> 15) & 0b11111:05b}_{(ins >> 12) & 0b111:03b}_{(ins >> 7) & 0b11111:05b}_{ins & 0b1111111:07b}"); + f"0b{ins >> 25 :07b}_{(ins >> 20) & 0b11111:05b}_{(ins >> 15) & 0b11111:05b}_{(ins >> 12) & 0b111:03b}_{(ins >> 7) & 0b11111:05b}_{ins & 0b1111111:07b}" + ) STATIC_INSN: Dict[int, Tuple[str, List[int], int]] = { 0x00000013: ("nop", [], 0x00000013), 0x00008067: ("ret", [], 0x00008067), - 0xfe010113: ("addi", [2, 2, -32], 0xfe010113), + 0xFE010113: ("addi", [2, 2, -32], 0xFE010113), 0x02010113: ("addi", [2, 2, 32], 0x02010113), 0x00100073: ("ebreak", [], 0x00100073), 0x00000073: ("ecall", [], 0x00000073), @@ -23,7 +24,7 @@ STATIC_INSN: Dict[int, Tuple[str, List[int], int]] = { def int_from_ins(insn: bytearray): - return int.from_bytes(insn, 'little') + return int.from_bytes(insn, "little") def name_from_insn(ins: int): @@ -45,7 +46,7 @@ def name_from_insn(ins: int): if isinstance(dec, str): return dec - if opcode == 0x1c and fun3 == 0: + if opcode == 0x1C and fun3 == 0: # we have ecall/ebreak token = imm110(ins) if token in dec: diff --git a/riscemu/decoder/formats.py b/riscemu/decoder/formats.py index 2f9af85..8eb807b 100644 --- a/riscemu/decoder/formats.py +++ b/riscemu/decoder/formats.py @@ -1,6 +1,7 @@ from typing import Dict, Callable, List, Union from .regs import RISCV_REGS + def op(ins: int): return (ins >> 2) & 31 @@ -46,7 +47,12 @@ def imm_b(ins: int): lower = rd(ins) higher = funct7(ins) - num = (lower & 0b11110) + ((higher & 0b0111111) << 5) + ((lower & 1) << 11) + ((higher >> 6) << 12) + num = ( + (lower & 0b11110) + + ((higher & 0b0111111) << 5) + + ((lower & 1) << 11) + + ((higher >> 6) << 12) + ) return sign_extend(num, 13) @@ -56,10 +62,11 @@ def imm_u(ins: int): def imm_j(ins: int): return sign_extend( - (((ins >> 21) & 0b1111111111) << 1) + - (((ins >> 20) & 1) << 11) + - (((ins >> 12) & 0b11111111) << 12) + - (((ins >> 31) & 1) << 20), 21 + (((ins >> 21) & 0b1111111111) << 1) + + (((ins >> 20) & 1) << 11) + + (((ins >> 12) & 0b11111111) << 12) + + (((ins >> 31) & 1) << 20), + 21, ) @@ -111,7 +118,7 @@ INSTRUCTION_ARGS_DECODER: Dict[int, Callable[[int], List[int]]] = { 0x0D: decode_u, 0x18: decode_b, 0x19: decode_i, - 0x1b: decode_j, - 0x1c: decode_i_unsigned, - 0b1011: decode_r + 0x1B: decode_j, + 0x1C: decode_i_unsigned, + 0b1011: decode_r, } diff --git a/riscemu/decoder/formatter.py b/riscemu/decoder/formatter.py index d2e5dbd..ec78343 100644 --- a/riscemu/decoder/formatter.py +++ b/riscemu/decoder/formatter.py @@ -1,5 +1,15 @@ -from .formats import INSTRUCTION_ARGS_DECODER, op, decode_i, decode_r, decode_u, decode_b, decode_j, decode_s, \ - decode_i_shamt, decode_i_unsigned +from .formats import ( + INSTRUCTION_ARGS_DECODER, + op, + decode_i, + decode_r, + decode_u, + decode_b, + decode_j, + decode_s, + decode_i_shamt, + decode_i_unsigned, +) from .regs import RISCV_REGS @@ -9,9 +19,9 @@ def int_to_hex(num: int): return f"0x{num:x}" -def format_ins(ins: int, name: str, fmt: str = 'int'): +def format_ins(ins: int, name: str, fmt: str = "int"): opcode = op(ins) - if fmt == 'hex': + if fmt == "hex": fmt = int_to_hex else: fmt = str @@ -20,7 +30,7 @@ def format_ins(ins: int, name: str, fmt: str = 'int'): return f"{name} " decoder = INSTRUCTION_ARGS_DECODER[opcode] - if name in ('ecall', 'ebreak', 'mret', 'sret', 'uret'): + if name in ("ecall", "ebreak", "mret", "sret", "uret"): return name if opcode in (0x8, 0x0): r1, r2, imm = decoder(ins) diff --git a/riscemu/decoder/instruction_table.py b/riscemu/decoder/instruction_table.py index 84dbd2e..9872ce9 100644 --- a/riscemu/decoder/instruction_table.py +++ b/riscemu/decoder/instruction_table.py @@ -4,7 +4,7 @@ from .formats import * tbl = lambda: defaultdict(tbl) RV32 = tbl() -RV32[0x1b] = "jal" +RV32[0x1B] = "jal" RV32[0x0D] = "lui" RV32[0x05] = "auipc" RV32[0x19][0] = "jalr" @@ -36,26 +36,26 @@ RV32[0x08][0] = "sb" RV32[0x08][1] = "sh" RV32[0x08][2] = "sw" -RV32[0x1c][1] = "csrrw" -RV32[0x1c][2] = "csrrs" -RV32[0x1c][3] = "csrrc" -RV32[0x1c][5] = "csrrwi" -RV32[0x1c][6] = "csrrsi" -RV32[0x1c][7] = "csrrci" +RV32[0x1C][1] = "csrrw" +RV32[0x1C][2] = "csrrs" +RV32[0x1C][3] = "csrrc" +RV32[0x1C][5] = "csrrwi" +RV32[0x1C][6] = "csrrsi" +RV32[0x1C][7] = "csrrci" -RV32[0x1c][0][0] = "ecall" -RV32[0x1c][0][1] = "ebreak" +RV32[0x1C][0][0] = "ecall" +RV32[0x1C][0][1] = "ebreak" -RV32[0x0C][0][0] = "add" +RV32[0x0C][0][0] = "add" RV32[0x0C][0][32] = "sub" -RV32[0x0C][1][0] = "sll" -RV32[0x0C][2][0] = "slt" -RV32[0x0C][3][0] = "sltu" -RV32[0x0C][4][0] = "xor" -RV32[0x0C][5][0] = "srl" +RV32[0x0C][1][0] = "sll" +RV32[0x0C][2][0] = "slt" +RV32[0x0C][3][0] = "sltu" +RV32[0x0C][4][0] = "xor" +RV32[0x0C][5][0] = "srl" RV32[0x0C][5][32] = "sra" -RV32[0x0C][6][0] = "or" -RV32[0x0C][7][0] = "and" +RV32[0x0C][6][0] = "or" +RV32[0x0C][7][0] = "and" # rv32m RV32[0x0C][0][1] = "mul" diff --git a/riscemu/decoder/regs.py b/riscemu/decoder/regs.py index 44d70ac..39318d5 100644 --- a/riscemu/decoder/regs.py +++ b/riscemu/decoder/regs.py @@ -1,6 +1,34 @@ RISCV_REGS = [ - 'zero', 'ra', 'sp', 'gp', 'tp', 't0', 't1', 't2', - 's0', 's1', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', - 's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11', - 't3', 't4', 't5', 't6' + "zero", + "ra", + "sp", + "gp", + "tp", + "t0", + "t1", + "t2", + "s0", + "s1", + "a0", + "a1", + "a2", + "a3", + "a4", + "a5", + "a6", + "a7", + "s2", + "s3", + "s4", + "s5", + "s6", + "s7", + "s8", + "s9", + "s10", + "s11", + "t3", + "t4", + "t5", + "t6", ] diff --git a/riscemu/helpers.py b/riscemu/helpers.py index 25cc53a..a7f8dc6 100644 --- a/riscemu/helpers.py +++ b/riscemu/helpers.py @@ -25,16 +25,19 @@ def parse_numeric_argument(arg: str) -> int: parse hex or int strings """ try: - if arg.lower().startswith('0x'): + if arg.lower().startswith("0x"): return int(arg, 16) return int(arg) except ValueError as ex: - raise ParseException('Invalid immediate argument \"{}\", maybe missing symbol?'.format(arg), (arg, ex)) + raise ParseException( + 'Invalid immediate argument "{}", maybe missing symbol?'.format(arg), + (arg, ex), + ) 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)] + return [my_list[i : i + chunk_size] for i in range(0, len(my_list), chunk_size)] def apply_highlight(item, ind, hi_ind): @@ -47,26 +50,31 @@ def apply_highlight(item, ind, hi_ind): def highlight_in_list(items, hi_ind, joiner=" "): - return joiner.join([apply_highlight(item, i, hi_ind) for i, item in enumerate(items)]) + return joiner.join( + [apply_highlight(item, i, hi_ind) for i, item in enumerate(items)] + ) def format_bytes(byte_arr: bytearray, fmt: str, group: int = 1, highlight: int = -1): """Format byte array as per fmt. Group into groups of size `group`, and highlight index `highlight`.""" chunks = create_chunks(byte_arr, group) - if fmt == 'hex': - return highlight_in_list(['0x{}'.format(ch.hex()) for ch in chunks], highlight) - if fmt == 'int': + if fmt == "hex": + 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(Int32(ch)) for ch in chunks], highlight) - if fmt == 'uint': + return highlight_in_list( + [("{:0" + spc + "d}").format(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(UInt32(ch)) for ch in chunks], - highlight) - if fmt == 'char': + return highlight_in_list( + [("{:0" + spc + "d}").format(UInt32(ch)) for ch in chunks], highlight + ) + if fmt == "char": return highlight_in_list((repr(chr(b))[1:-1] for b in byte_arr), highlight, "") -T = TypeVar('T') +T = TypeVar("T") class Peekable(Generic[T], Iterator[T]): @@ -100,6 +108,10 @@ class Peekable(Generic[T], Iterator[T]): def get_section_base_name(section_name: str) -> str: - if '.' not in section_name: - print(FMT_PARSE + f"Invalid section {section_name}, not starting with a dot!" + FMT_NONE) - return '.' + section_name.split('.')[1] + if "." not in section_name: + print( + FMT_PARSE + + f"Invalid section {section_name}, not starting with a dot!" + + FMT_NONE + ) + return "." + section_name.split(".")[1] diff --git a/riscemu/instructions/RV32A.py b/riscemu/instructions/RV32A.py index af92130..2d0ef4e 100644 --- a/riscemu/instructions/RV32A.py +++ b/riscemu/instructions/RV32A.py @@ -10,52 +10,52 @@ class RV32A(InstructionSet): for this? """ - def instruction_lr_w(self, ins: 'Instruction'): + def instruction_lr_w(self, ins: "Instruction"): INS_NOT_IMPLEMENTED(ins) - def instruction_sc_w(self, ins: 'Instruction'): + def instruction_sc_w(self, ins: "Instruction"): INS_NOT_IMPLEMENTED(ins) - def instruction_amoswap_w(self, ins: 'Instruction'): + def instruction_amoswap_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) - if dest == 'zero': + if dest == "zero": self.mmu.write(addr, val.to_bytes()) else: 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'): + def instruction_amoadd_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) old = Int32(self.mmu.read(addr, 4)) self.mmu.write(addr, (old + val).to_bytes(4)) self.regs.set(dest, old) - def instruction_amoand_w(self, ins: 'Instruction'): + def instruction_amoand_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) old = Int32(self.mmu.read(addr, 4)) self.mmu.write(addr, (old & val).to_bytes(4)) self.regs.set(dest, old) - def instruction_amoor_w(self, ins: 'Instruction'): + def instruction_amoor_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) old = Int32(self.mmu.read(addr, 4)) self.mmu.write(addr, (old | val).to_bytes(4)) self.regs.set(dest, old) - def instruction_amoxor_w(self, ins: 'Instruction'): + def instruction_amoxor_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) old = Int32(self.mmu.read(addr, 4)) self.mmu.write(addr, (old ^ val).to_bytes(4)) self.regs.set(dest, old) - def instruction_amomax_w(self, ins: 'Instruction'): + def instruction_amomax_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) old = Int32(self.mmu.read(addr, 4)) self.mmu.write(addr, max(old, val).to_bytes(4)) self.regs.set(dest, old) - def instruction_amomaxu_w(self, ins: 'Instruction'): + def instruction_amomaxu_w(self, ins: "Instruction"): val: UInt32 dest, addr, val = self.parse_rd_rs_rs(ins, signed=False) old = UInt32(self.mmu.read(addr, 4)) @@ -63,13 +63,13 @@ class RV32A(InstructionSet): self.mmu.write(addr, max(old, val).to_bytes()) self.regs.set(dest, old) - def instruction_amomin_w(self, ins: 'Instruction'): + def instruction_amomin_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) old = Int32(self.mmu.read(addr, 4)) self.mmu.write(addr, min(old, val).to_bytes(4)) self.regs.set(dest, old) - def instruction_amominu_w(self, ins: 'Instruction'): + def instruction_amominu_w(self, ins: "Instruction"): val: UInt32 dest, addr, val = self.parse_rd_rs_rs(ins, signed=False) old = UInt32(self.mmu.read(addr, 4)) diff --git a/riscemu/instructions/RV32I.py b/riscemu/instructions/RV32I.py index a2cced2..f9c7107 100644 --- a/riscemu/instructions/RV32I.py +++ b/riscemu/instructions/RV32I.py @@ -22,241 +22,186 @@ class RV32I(InstructionSet): See https://maxvytech.com/images/RV32I-11-2018.pdf for a more detailed overview """ - def instruction_lb(self, ins: 'Instruction'): + def instruction_lb(self, ins: "Instruction"): rd, addr = self.parse_mem_ins(ins) self.regs.set(rd, Int32.sign_extend(self.mmu.read(addr.unsigned_value, 1), 8)) - def instruction_lh(self, ins: 'Instruction'): + def instruction_lh(self, ins: "Instruction"): rd, addr = self.parse_mem_ins(ins) self.regs.set(rd, Int32.sign_extend(self.mmu.read(addr.unsigned_value, 2), 16)) - def instruction_lw(self, ins: 'Instruction'): + def instruction_lw(self, ins: "Instruction"): rd, addr = self.parse_mem_ins(ins) self.regs.set(rd, Int32(self.mmu.read(addr.unsigned_value, 4))) - def instruction_lbu(self, ins: 'Instruction'): + def instruction_lbu(self, ins: "Instruction"): rd, addr = self.parse_mem_ins(ins) self.regs.set(rd, Int32(self.mmu.read(addr.unsigned_value, 1))) - def instruction_lhu(self, ins: 'Instruction'): + def instruction_lhu(self, ins: "Instruction"): rd, addr = self.parse_mem_ins(ins) self.regs.set(rd, Int32(self.mmu.read(addr.unsigned_value, 2))) - def instruction_sb(self, ins: 'Instruction'): + def instruction_sb(self, ins: "Instruction"): rd, addr = self.parse_mem_ins(ins) self.mmu.write(addr.unsigned_value, 1, self.regs.get(rd).to_bytes(1)) - def instruction_sh(self, ins: 'Instruction'): + def instruction_sh(self, ins: "Instruction"): rd, addr = self.parse_mem_ins(ins) self.mmu.write(addr.unsigned_value, 2, self.regs.get(rd).to_bytes(2)) - def instruction_sw(self, ins: 'Instruction'): + def instruction_sw(self, ins: "Instruction"): rd, addr = self.parse_mem_ins(ins) self.mmu.write(addr.unsigned_value, 4, self.regs.get(rd).to_bytes(4)) - def instruction_sll(self, ins: 'Instruction'): + def instruction_sll(self, ins: "Instruction"): ASSERT_LEN(ins.args, 3) dst = ins.get_reg(0) src1 = ins.get_reg(1) src2 = ins.get_reg(2) - self.regs.set( - dst, - self.regs.get(src1) << (self.regs.get(src2) & 0b11111) - ) + self.regs.set(dst, self.regs.get(src1) << (self.regs.get(src2) & 0b11111)) - def instruction_slli(self, ins: 'Instruction'): + def instruction_slli(self, ins: "Instruction"): ASSERT_LEN(ins.args, 3) dst = ins.get_reg(0) src1 = ins.get_reg(1) imm = ins.get_imm(2) - self.regs.set( - dst, - self.regs.get(src1) << (imm & 0b11111) - ) + self.regs.set(dst, self.regs.get(src1) << (imm & 0b11111)) - def instruction_srl(self, ins: 'Instruction'): + def instruction_srl(self, ins: "Instruction"): ASSERT_LEN(ins.args, 3) dst = ins.get_reg(0) src1 = ins.get_reg(1) src2 = ins.get_reg(2) self.regs.set( - dst, - self.regs.get(src1).shift_right_logical(self.regs.get(src2) & 0b11111) + dst, self.regs.get(src1).shift_right_logical(self.regs.get(src2) & 0b11111) ) - def instruction_srli(self, ins: 'Instruction'): + def instruction_srli(self, ins: "Instruction"): ASSERT_LEN(ins.args, 3) dst = ins.get_reg(0) src1 = ins.get_reg(1) imm = ins.get_imm(2) - self.regs.set( - dst, - self.regs.get(src1).shift_right_logical(imm & 0b11111) - ) + self.regs.set(dst, self.regs.get(src1).shift_right_logical(imm & 0b11111)) - def instruction_sra(self, ins: 'Instruction'): + def instruction_sra(self, ins: "Instruction"): ASSERT_LEN(ins.args, 3) dst = ins.get_reg(0) src1 = ins.get_reg(1) src2 = ins.get_reg(2) - self.regs.set( - dst, - self.regs.get(src1) >> (self.regs.get(src2) & 0b11111) - ) + self.regs.set(dst, self.regs.get(src1) >> (self.regs.get(src2) & 0b11111)) - def instruction_srai(self, ins: 'Instruction'): + def instruction_srai(self, ins: "Instruction"): ASSERT_LEN(ins.args, 3) dst = ins.get_reg(0) src1 = ins.get_reg(1) imm = ins.get_imm(2) - self.regs.set( - dst, - self.regs.get(src1) >> (imm & 0b11111) - ) + self.regs.set(dst, self.regs.get(src1) >> (imm & 0b11111)) - def instruction_add(self, ins: 'Instruction'): + def instruction_add(self, ins: "Instruction"): # FIXME: once configuration is figured out, add flag to support immediate arg in add instruction dst, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set( - dst, - rs1 + rs2 - ) + self.regs.set(dst, rs1 + rs2) - def instruction_addi(self, ins: 'Instruction'): + def instruction_addi(self, ins: "Instruction"): dst, rs1, imm = self.parse_rd_rs_imm(ins) - self.regs.set( - dst, - rs1 + imm - ) + self.regs.set(dst, rs1 + imm) - def instruction_sub(self, ins: 'Instruction'): + def instruction_sub(self, ins: "Instruction"): dst, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set( - dst, - rs1 - rs2 - ) + self.regs.set(dst, rs1 - rs2) - def instruction_lui(self, ins: 'Instruction'): + def instruction_lui(self, ins: "Instruction"): ASSERT_LEN(ins.args, 2) reg = ins.get_reg(0) imm = UInt32(ins.get_imm(1) << 12) self.regs.set(reg, Int32(imm)) - def instruction_auipc(self, ins: 'Instruction'): + def instruction_auipc(self, ins: "Instruction"): ASSERT_LEN(ins.args, 2) reg = ins.get_reg(0) imm = UInt32(ins.get_imm(1) << 12) self.regs.set(reg, imm.signed() + self.pc) - def instruction_xor(self, ins: 'Instruction'): + def instruction_xor(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set( - rd, - rs1 ^ rs2 - ) + self.regs.set(rd, rs1 ^ rs2) - def instruction_xori(self, ins: 'Instruction'): + def instruction_xori(self, ins: "Instruction"): rd, rs1, imm = self.parse_rd_rs_imm(ins) - self.regs.set( - rd, - rs1 ^ imm - ) + self.regs.set(rd, rs1 ^ imm) - def instruction_or(self, ins: 'Instruction'): + def instruction_or(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set( - rd, - rs1 | rs2 - ) + self.regs.set(rd, rs1 | rs2) - def instruction_ori(self, ins: 'Instruction'): + def instruction_ori(self, ins: "Instruction"): rd, rs1, imm = self.parse_rd_rs_imm(ins) - self.regs.set( - rd, - rs1 | imm - ) + self.regs.set(rd, rs1 | imm) - def instruction_and(self, ins: 'Instruction'): + def instruction_and(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set( - rd, - rs1 & rs2 - ) + self.regs.set(rd, rs1 & rs2) - def instruction_andi(self, ins: 'Instruction'): + def instruction_andi(self, ins: "Instruction"): rd, rs1, imm = self.parse_rd_rs_imm(ins) - self.regs.set( - rd, - rs1 & imm - ) + self.regs.set(rd, rs1 & imm) - def instruction_slt(self, ins: 'Instruction'): + def instruction_slt(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set( - rd, - Int32(int(rs1 < rs2)) - ) + self.regs.set(rd, Int32(int(rs1 < rs2))) - def instruction_slti(self, ins: 'Instruction'): + def instruction_slti(self, ins: "Instruction"): rd, rs1, imm = self.parse_rd_rs_imm(ins) - self.regs.set( - rd, - Int32(int(rs1 < imm)) - ) + self.regs.set(rd, Int32(int(rs1 < imm))) - def instruction_sltu(self, ins: 'Instruction'): + def instruction_sltu(self, ins: "Instruction"): dst, rs1, rs2 = self.parse_rd_rs_rs(ins, signed=False) - self.regs.set( - dst, - Int32(int(rs1 < rs2)) - ) + self.regs.set(dst, Int32(int(rs1 < rs2))) - def instruction_sltiu(self, ins: 'Instruction'): + def instruction_sltiu(self, ins: "Instruction"): dst, rs1, imm = self.parse_rd_rs_imm(ins, signed=False) - self.regs.set( - dst, - Int32(int(rs1 < imm)) - ) + self.regs.set(dst, Int32(int(rs1 < imm))) - def instruction_beq(self, ins: 'Instruction'): + def instruction_beq(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 == rs2: self.pc = dst.unsigned_value - def instruction_bne(self, ins: 'Instruction'): + def instruction_bne(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 != rs2: self.pc = dst.unsigned_value - def instruction_blt(self, ins: 'Instruction'): + def instruction_blt(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 < rs2: self.pc = dst.unsigned_value - def instruction_bge(self, ins: 'Instruction'): + def instruction_bge(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 >= rs2: self.pc = dst.unsigned_value - def instruction_bltu(self, ins: 'Instruction'): + def instruction_bltu(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) if rs1 < rs2: self.pc = dst.unsigned_value - def instruction_bgeu(self, ins: 'Instruction'): + def instruction_bgeu(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) if rs1 >= rs2: self.pc = dst.unsigned_value # technically deprecated - def instruction_j(self, ins: 'Instruction'): + def instruction_j(self, ins: "Instruction"): ASSERT_LEN(ins.args, 1) addr = ins.get_imm(0) self.pc = addr - def instruction_jal(self, ins: 'Instruction'): - reg = 'ra' # default register is ra + def instruction_jal(self, ins: "Instruction"): + reg = "ra" # default register is ra if len(ins.args) == 1: addr = ins.get_imm(0) else: @@ -266,58 +211,60 @@ class RV32I(InstructionSet): self.regs.set(reg, Int32(self.pc)) self.pc = addr - def instruction_jalr(self, ins: 'Instruction'): + def instruction_jalr(self, ins: "Instruction"): ASSERT_LEN(ins.args, 2) reg = ins.get_reg(0) addr = ins.get_imm(1) self.regs.set(reg, Int32(self.pc)) self.pc = addr - def instruction_ret(self, ins: 'Instruction'): + def instruction_ret(self, ins: "Instruction"): ASSERT_LEN(ins.args, 0) - self.pc = self.regs.get('ra').value + self.pc = self.regs.get("ra").value - def instruction_ecall(self, ins: 'Instruction'): + def instruction_ecall(self, ins: "Instruction"): self.instruction_scall(ins) - def instruction_ebreak(self, ins: 'Instruction'): + def instruction_ebreak(self, ins: "Instruction"): self.instruction_sbreak(ins) - def instruction_scall(self, ins: 'Instruction'): + def instruction_scall(self, ins: "Instruction"): ASSERT_LEN(ins.args, 0) if not isinstance(self.cpu, UserModeCPU): # FIXME: add exception for syscall not supported or something raise - syscall = Syscall(self.regs.get('a7'), self.cpu) + syscall = Syscall(self.regs.get("a7"), self.cpu) self.cpu.syscall_int.handle_syscall(syscall) - def instruction_sbreak(self, ins: 'Instruction'): + def instruction_sbreak(self, ins: "Instruction"): ASSERT_LEN(ins.args, 0) if self.cpu.conf.debug_instruction: - print(FMT_DEBUG + "Debug instruction encountered at 0x{:08X}".format(self.pc - 1) + FMT_NONE) + print( + FMT_DEBUG + + "Debug instruction encountered at 0x{:08X}".format(self.pc - 1) + + FMT_NONE + ) raise LaunchDebuggerException() - def instruction_nop(self, ins: 'Instruction'): + def instruction_nop(self, ins: "Instruction"): ASSERT_LEN(ins.args, 0) pass - def instruction_li(self, ins: 'Instruction'): + def instruction_li(self, ins: "Instruction"): ASSERT_LEN(ins.args, 2) reg = ins.get_reg(0) immediate = ins.get_imm(1) self.regs.set(reg, Int32(immediate)) - def instruction_la(self, ins: 'Instruction'): + def instruction_la(self, ins: "Instruction"): ASSERT_LEN(ins.args, 2) reg = ins.get_reg(0) immediate = ins.get_imm(1) self.regs.set(reg, Int32(immediate)) - def instruction_mv(self, ins: 'Instruction'): + def instruction_mv(self, ins: "Instruction"): ASSERT_LEN(ins.args, 2) rd, rs = ins.get_reg(0), ins.get_reg(1) self.regs.set(rd, self.regs.get(rs)) - - diff --git a/riscemu/instructions/RV32M.py b/riscemu/instructions/RV32M.py index dda5ee4..0512ca4 100644 --- a/riscemu/instructions/RV32M.py +++ b/riscemu/instructions/RV32M.py @@ -12,50 +12,33 @@ class RV32M(InstructionSet): """ The RV32M Instruction set, containing multiplication and division instructions """ - def instruction_mul(self, ins: 'Instruction'): + + def instruction_mul(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set( - rd, - rs1 * rs2 - ) + self.regs.set(rd, rs1 * rs2) - def instruction_mulh(self, ins: 'Instruction'): + def instruction_mulh(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set( - rd, - (rs1 * rs2) >> 32 - ) + self.regs.set(rd, (rs1 * rs2) >> 32) - def instruction_mulhsu(self, ins: 'Instruction'): + def instruction_mulhsu(self, ins: "Instruction"): INS_NOT_IMPLEMENTED(ins) - def instruction_mulhu(self, ins: 'Instruction'): + def instruction_mulhu(self, ins: "Instruction"): INS_NOT_IMPLEMENTED(ins) - def instruction_div(self, ins: 'Instruction'): + def instruction_div(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set( - rd, - rs1 // rs2 - ) + self.regs.set(rd, rs1 // rs2) - def instruction_divu(self, ins: 'Instruction'): + def instruction_divu(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins, signed=False) - self.regs.set( - rd, - rs1 // rs2 - ) + self.regs.set(rd, rs1 // rs2) - def instruction_rem(self, ins: 'Instruction'): + def instruction_rem(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set( - rd, - rs1 % rs2 - ) + self.regs.set(rd, rs1 % rs2) - def instruction_remu(self, ins: 'Instruction'): + def instruction_remu(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins, signed=False) - self.regs.set( - rd, - rs1 % rs2 - ) + self.regs.set(rd, rs1 % rs2) diff --git a/riscemu/instructions/RV_Debug.py b/riscemu/instructions/RV_Debug.py index 1deeb9b..ad31181 100644 --- a/riscemu/instructions/RV_Debug.py +++ b/riscemu/instructions/RV_Debug.py @@ -8,7 +8,11 @@ class RV_Debug(InstructionSet): def instruction_print_uint(self, ins: Instruction): reg = ins.get_reg(0) - print("register {} contains value {}".format(reg, self.regs.get(reg).unsigned_value)) + print( + "register {} contains value {}".format( + reg, self.regs.get(reg).unsigned_value + ) + ) def instruction_print_hex(self, ins: Instruction): reg = ins.get_reg(0) @@ -16,4 +20,8 @@ class RV_Debug(InstructionSet): def instruction_print_uhex(self, ins: Instruction): reg = ins.get_reg(0) - print("register {} contains value {}".format(reg, hex(self.regs.get(reg).unsigned_value))) + print( + "register {} contains value {}".format( + reg, hex(self.regs.get(reg).unsigned_value) + ) + ) diff --git a/riscemu/instructions/__init__.py b/riscemu/instructions/__init__.py index 45b5566..30e1f18 100644 --- a/riscemu/instructions/__init__.py +++ b/riscemu/instructions/__init__.py @@ -12,6 +12,4 @@ from .RV32I import RV32I from .RV32A import RV32A from .RV_Debug import RV_Debug -InstructionSetDict = { - v.__name__: v for v in [RV32I, RV32M, RV32A, RV_Debug] -} +InstructionSetDict = {v.__name__: v for v in [RV32I, RV32M, RV32A, RV_Debug]} diff --git a/riscemu/instructions/instruction_set.py b/riscemu/instructions/instruction_set.py index 191686c..5e26f86 100644 --- a/riscemu/instructions/instruction_set.py +++ b/riscemu/instructions/instruction_set.py @@ -23,23 +23,21 @@ class InstructionSet(ABC): instructions containing a dot '.' should replace it with an underscore. """ - def __init__(self, cpu: 'CPU'): + def __init__(self, cpu: "CPU"): """Create a new instance of the Instruction set. This requires access to a CPU, and grabs vertain things from it such as access to the MMU and registers. """ self.name = self.__class__.__name__ self.cpu = cpu - def load(self) -> Dict[str, Callable[['Instruction'], None]]: + def load(self) -> Dict[str, Callable[["Instruction"], None]]: """ This is called by the CPU once it instantiates this instruction set It returns a dictionary of all instructions in this instruction set, pointing to the correct handler for it """ - return { - name: ins for name, ins in self.get_instructions() - } + return {name: ins for name, ins in self.get_instructions()} def get_instructions(self): """ @@ -48,10 +46,10 @@ class InstructionSet(ABC): converts underscores in names to dots """ for member in dir(self): - if member.startswith('instruction_'): - yield member[12:].replace('_', '.'), getattr(self, member) + if member.startswith("instruction_"): + yield member[12:].replace("_", "."), getattr(self, member) - def parse_mem_ins(self, ins: 'Instruction') -> Tuple[str, Int32]: + 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) @@ -69,51 +67,69 @@ class InstructionSet(ABC): rd = ins.get_reg(0) return rd, rs + imm - def parse_rd_rs_rs(self, ins: 'Instruction', signed=True) -> Tuple[str, Int32, Int32]: + def parse_rd_rs_rs( + self, ins: "Instruction", signed=True + ) -> Tuple[str, Int32, Int32]: """ Assumes the command is in rd, rs1, rs2 format Returns the name of rd, and the values in rs1 and rs2 """ ASSERT_LEN(ins.args, 3) if signed: - return ins.get_reg(0), \ - Int32(self.get_reg_content(ins, 1)), \ - Int32(self.get_reg_content(ins, 2)) + return ( + ins.get_reg(0), + Int32(self.get_reg_content(ins, 1)), + Int32(self.get_reg_content(ins, 2)), + ) else: - return ins.get_reg(0), \ - UInt32(self.get_reg_content(ins, 1)), \ - UInt32(self.get_reg_content(ins, 2)) + return ( + ins.get_reg(0), + 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, Int32, Int32]: + def parse_rd_rs_imm( + self, ins: "Instruction", signed=True + ) -> Tuple[str, Int32, Int32]: """ Assumes the command is in rd, rs, imm format Returns the name of rd, the value in rs and the immediate imm """ ASSERT_LEN(ins.args, 3) if signed: - return ins.get_reg(0), \ - Int32(self.get_reg_content(ins, 1)), \ - Int32(ins.get_imm(2)) + return ( + ins.get_reg(0), + Int32(self.get_reg_content(ins, 1)), + Int32(ins.get_imm(2)), + ) else: - return ins.get_reg(0), \ - UInt32(self.get_reg_content(ins, 1)), \ - UInt32(ins.get_imm(2)) + return ( + ins.get_reg(0), + UInt32(self.get_reg_content(ins, 1)), + UInt32(ins.get_imm(2)), + ) - def parse_rs_rs_imm(self, ins: 'Instruction', signed=True) -> Tuple[Int32, Int32, Int32]: + def parse_rs_rs_imm( + self, ins: "Instruction", signed=True + ) -> Tuple[Int32, Int32, Int32]: """ Assumes the command is in rs1, rs2, imm format Returns the values in rs1, rs2 and the immediate imm """ if signed: - return Int32(self.get_reg_content(ins, 0)), \ - Int32(self.get_reg_content(ins, 1)), \ - Int32(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 UInt32(self.get_reg_content(ins, 0)), \ - UInt32(self.get_reg_content(ins, 1)), \ - UInt32(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) -> Int32: + def get_reg_content(self, ins: "Instruction", ind: int) -> Int32: """ get the register name from ins and then return the register contents """ @@ -140,6 +156,5 @@ class InstructionSet(ABC): def __repr__(self): return "InstructionSet[{}] with {} instructions".format( - self.__class__.__name__, - len(list(self.get_instructions())) + self.__class__.__name__, len(list(self.get_instructions())) ) diff --git a/riscemu/interactive.py b/riscemu/interactive.py index 2a85da3..0d10ee9 100644 --- a/riscemu/interactive.py +++ b/riscemu/interactive.py @@ -3,21 +3,29 @@ import sys from riscemu import RunConfig from riscemu.types import InstructionMemorySection, SimpleInstruction, Program -if __name__ == '__main__': +if __name__ == "__main__": from .CPU import UserModeCPU from .instructions import InstructionSetDict from .debug import launch_debug_session cpu = UserModeCPU(list(InstructionSetDict.values()), RunConfig(verbosity=4)) - program = Program('interactive session', base=0x100) + program = Program("interactive session", base=0x100) context = program.context - program.add_section(InstructionMemorySection([ - SimpleInstruction('ebreak', (), context, 0x100), - SimpleInstruction('addi', ('a0', 'zero', '0'), context, 0x104), - SimpleInstruction('addi', ('a7', 'zero', '93'), context, 0x108), - SimpleInstruction('scall', (), context, 0x10C), - ], '.text', context, program.name, 0x100)) + program.add_section( + InstructionMemorySection( + [ + SimpleInstruction("ebreak", (), context, 0x100), + SimpleInstruction("addi", ("a0", "zero", "0"), context, 0x104), + SimpleInstruction("addi", ("a7", "zero", "93"), context, 0x108), + SimpleInstruction("scall", (), context, 0x10C), + ], + ".text", + context, + program.name, + 0x100, + ) + ) cpu.load_program(program) diff --git a/riscemu/parser.py b/riscemu/parser.py index 86759ec..39c18a8 100644 --- a/riscemu/parser.py +++ b/riscemu/parser.py @@ -16,28 +16,32 @@ from .types.exceptions import ParseException def parse_instruction(token: Token, args: Tuple[str], context: ParseContext): if context.section is None: - context.new_section('.text', MemorySectionType.Instructions) + context.new_section(".text", MemorySectionType.Instructions) if context.section.type != MemorySectionType.Instructions: - raise ParseException("{} {} encountered in invalid context: {}".format(token, args, context)) - ins = SimpleInstruction(token.value, args, context.context, context.current_address()) + raise ParseException( + "{} {} encountered in invalid context: {}".format(token, args, context) + ) + ins = SimpleInstruction( + token.value, args, context.context, context.current_address() + ) context.section.data.append(ins) def parse_label(token: Token, args: Tuple[str], context: ParseContext): name = token.value[:-1] - if re.match(r'^\d+$', name): + if re.match(r"^\d+$", name): # relative label: context.context.numbered_labels[name].append(context.current_address()) else: if name in context.context.labels: - print(FMT_PARSE + 'Warn: Symbol {} defined twice!'.format(name)) + print(FMT_PARSE + "Warn: Symbol {} defined twice!".format(name)) context.add_label(name, context.current_address(), is_relative=True) PARSERS: Dict[TokenType, Callable[[Token, Tuple[str], ParseContext], None]] = { TokenType.PSEUDO_OP: AssemblerDirectives.handle_instruction, TokenType.LABEL: parse_label, - TokenType.INSTRUCTION_NAME: parse_instruction + TokenType.INSTRUCTION_NAME: parse_instruction, } @@ -58,7 +62,9 @@ def parse_tokens(name: str, tokens_iter: Iterable[Token]) -> Program: return context.finalize() -def composite_tokenizer(tokens_iter: Iterable[Token]) -> Iterable[Tuple[Token, Tuple[str]]]: +def composite_tokenizer( + tokens_iter: Iterable[Token], +) -> Iterable[Tuple[Token, Tuple[str]]]: """ Convert an iterator over tokens into an iterator over tuples: (token, list(token)) @@ -71,7 +77,11 @@ def composite_tokenizer(tokens_iter: Iterable[Token]) -> Iterable[Tuple[Token, T while not tokens.is_empty(): token = next(tokens) - if token.type in (TokenType.PSEUDO_OP, TokenType.LABEL, TokenType.INSTRUCTION_NAME): + if token.type in ( + TokenType.PSEUDO_OP, + TokenType.LABEL, + TokenType.INSTRUCTION_NAME, + ): yield token, tuple(take_arguments(tokens)) @@ -106,8 +116,9 @@ class AssemblyFileLoader(ProgramLoader): The AssemblyFileLoader loads .asm, .S and .s files by default, and acts as a weak fallback to all other filetypes. """ + def parse(self) -> Program: - with open(self.source_path, 'r') as f: + with open(self.source_path, "r") as f: return parse_tokens(self.filename, tokenize(f)) def parse_io(self, io): @@ -123,7 +134,7 @@ class AssemblyFileLoader(ProgramLoader): :return: """ # gcc recognizes these line endings as assembly. So we will do too. - if source_path.split('.')[-1] in ('asm', 'S', 's'): + if source_path.split(".")[-1] in ("asm", "S", "s"): return 1 return 0.01 diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index fbd83c6..a9fe7be 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -12,6 +12,7 @@ class CSR: """ This holds all Control and Status Registers (CSR) """ + regs: Dict[int, UInt32] """ All Control and Status Registers are stored here @@ -52,7 +53,9 @@ class CSR: return self.virtual_regs[addr]() return self.regs[addr] - def set_listener(self, addr: Union[str, int], listener: Callable[[UInt32, UInt32], 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)) @@ -73,11 +76,11 @@ class CSR: """ size = 2 if name in MSTATUS_LEN_2 else 1 off = MSTATUS_OFFSETS[name] - mask = (2 ** size - 1) << off - old_val = self.get('mstatus') + mask = (2**size - 1) << off + old_val = self.get("mstatus") erased = old_val & (~mask) new_val = erased | (val << off) - self.set('mstatus', new_val) + self.set("mstatus", new_val) def get_mstatus(self, name) -> UInt32: if not self.mstatus_cache_dirty and name in self.mstatus_cache: @@ -85,8 +88,8 @@ class CSR: size = 2 if name in MSTATUS_LEN_2 else 1 off = MSTATUS_OFFSETS[name] - mask = (2 ** size - 1) << off - val = (self.get('mstatus') & mask) >> off + mask = (2**size - 1) << off + val = (self.get("mstatus") & mask) >> off if self.mstatus_cache_dirty: self.mstatus_cache = dict(name=val) else: diff --git a/riscemu/priv/CSRConsts.py b/riscemu/priv/CSRConsts.py index b8b75bb..96e6ecf 100644 --- a/riscemu/priv/CSRConsts.py +++ b/riscemu/priv/CSRConsts.py @@ -1,81 +1,81 @@ from typing import Dict, Tuple -MCAUSE_TRANSLATION: Dict[Tuple[int, int], str]= { - (1, 0): 'User software interrupt', - (1, 1): 'Supervisor software interrupt', - (1, 3): 'Machine software interrupt', - (1, 4): 'User timer interrupt', - (1, 5): 'Supervisor timer interrupt', - (1, 7): 'Machine timer interrupt', - (1, 8): 'User external interrupt', - (1, 9): 'Supervisor external interrupt', - (1, 11): 'Machine external interrupt', - (0, 0): 'Instruction address misaligned', - (0, 1): 'Instruction access fault', - (0, 2): 'Illegal instruction', - (0, 3): 'Breakpoint', - (0, 4): 'Load address misaligned', - (0, 5): 'Load access fault', - (0, 6): 'Store/AMO address misaligned', - (0, 7): 'Store/AMO access fault', - (0, 8): 'environment call from user mode', - (0, 9): 'environment call from supervisor mode', - (0, 11): 'environment call from machine mode', - (0, 12): 'Instruction page fault', - (0, 13): 'Load page fault', - (0, 15): 'Store/AMO page fault', +MCAUSE_TRANSLATION: Dict[Tuple[int, int], str] = { + (1, 0): "User software interrupt", + (1, 1): "Supervisor software interrupt", + (1, 3): "Machine software interrupt", + (1, 4): "User timer interrupt", + (1, 5): "Supervisor timer interrupt", + (1, 7): "Machine timer interrupt", + (1, 8): "User external interrupt", + (1, 9): "Supervisor external interrupt", + (1, 11): "Machine external interrupt", + (0, 0): "Instruction address misaligned", + (0, 1): "Instruction access fault", + (0, 2): "Illegal instruction", + (0, 3): "Breakpoint", + (0, 4): "Load address misaligned", + (0, 5): "Load access fault", + (0, 6): "Store/AMO address misaligned", + (0, 7): "Store/AMO access fault", + (0, 8): "environment call from user mode", + (0, 9): "environment call from supervisor mode", + (0, 11): "environment call from machine mode", + (0, 12): "Instruction page fault", + (0, 13): "Load page fault", + (0, 15): "Store/AMO page fault", } """ Assigns tuple (interrupt bit, exception code) to their respective readable names """ MSTATUS_OFFSETS: Dict[str, int] = { - 'uie': 0, - 'sie': 1, - 'mie': 3, - 'upie': 4, - 'spie': 5, - 'mpie': 7, - 'spp': 8, - 'mpp': 11, - 'fs': 13, - 'xs': 15, - 'mpriv': 17, - 'sum': 18, - 'mxr': 19, - 'tvm': 20, - 'tw': 21, - 'tsr': 22, - 'sd': 31 + "uie": 0, + "sie": 1, + "mie": 3, + "upie": 4, + "spie": 5, + "mpie": 7, + "spp": 8, + "mpp": 11, + "fs": 13, + "xs": 15, + "mpriv": 17, + "sum": 18, + "mxr": 19, + "tvm": 20, + "tw": 21, + "tsr": 22, + "sd": 31, } """ Offsets for all mstatus bits """ -MSTATUS_LEN_2 = ('mpp', 'fs', 'xs') +MSTATUS_LEN_2 = ("mpp", "fs", "xs") """ All mstatus parts that have length 2. All other mstatus parts have length 1 """ CSR_NAME_TO_ADDR: Dict[str, int] = { - 'mstatus': 0x300, - 'misa': 0x301, - 'mie': 0x304, - 'mtvec': 0x305, - 'mepc': 0x341, - 'mcause': 0x342, - 'mtval': 0x343, - 'mip': 0x344, - 'mvendorid': 0xF11, - 'marchid': 0xF12, - 'mimpid': 0xF13, - 'mhartid': 0xF14, - 'time': 0xc01, - 'timeh': 0xc81, - 'halt': 0x789, - 'mtimecmp': 0x780, - 'mtimecmph': 0x781, + "mstatus": 0x300, + "misa": 0x301, + "mie": 0x304, + "mtvec": 0x305, + "mepc": 0x341, + "mcause": 0x342, + "mtval": 0x343, + "mip": 0x344, + "mvendorid": 0xF11, + "marchid": 0xF12, + "mimpid": 0xF13, + "mhartid": 0xF14, + "time": 0xC01, + "timeh": 0xC81, + "halt": 0x789, + "mtimecmp": 0x780, + "mtimecmph": 0x781, } """ Translation for named registers -""" \ No newline at end of file +""" diff --git a/riscemu/priv/ElfLoader.py b/riscemu/priv/ElfLoader.py index 199c2ed..20988bf 100644 --- a/riscemu/priv/ElfLoader.py +++ b/riscemu/priv/ElfLoader.py @@ -11,7 +11,7 @@ if typing.TYPE_CHECKING: from elftools.elf.elffile import ELFFile from elftools.elf.sections import Section, SymbolTableSection -INCLUDE_SEC = ('.text', '.stack', '.bss', '.sdata', '.sbss') +INCLUDE_SEC = (".text", ".stack", ".bss", ".sdata", ".sbss") class ElfBinaryFileLoader(ProgramLoader): @@ -20,6 +20,7 @@ class ElfBinaryFileLoader(ProgramLoader): This loader respects local and global symbols. """ + program: Program def __init__(self, source_path: str, options: T_ParserOpts): @@ -28,8 +29,8 @@ class ElfBinaryFileLoader(ProgramLoader): @classmethod def can_parse(cls, source_path: str) -> float: - with open(source_path, 'rb') as f: - if f.read(4) == b'\x7f\x45\x4c\x46': + with open(source_path, "rb") as f: + if f.read(4) == b"\x7f\x45\x4c\x46": return 1 return 0 @@ -42,23 +43,33 @@ class ElfBinaryFileLoader(ProgramLoader): from elftools.elf.elffile import ELFFile from elftools.elf.sections import Section, SymbolTableSection - with open(self.source_path, 'rb') as f: - print(FMT_ELF + "[ElfLoader] Loading elf executable from: {}".format(self.source_path) + FMT_NONE) + with open(self.source_path, "rb") as f: + print( + FMT_ELF + + "[ElfLoader] Loading elf executable from: {}".format( + self.source_path + ) + + FMT_NONE + ) self._read_elf(ELFFile(f)) except ImportError as e: - print(FMT_PARSE + "[ElfLoader] Cannot load elf files without PyElfTools package! You can install them " - "using pip install pyelftools!" + FMT_NONE) + print( + FMT_PARSE + + "[ElfLoader] Cannot load elf files without PyElfTools package! You can install them " + "using pip install pyelftools!" + FMT_NONE + ) raise e return self.program - def _read_elf(self, elf: 'ELFFile'): - if not elf.header.e_machine == 'EM_RISCV': + def _read_elf(self, elf: "ELFFile"): + if not elf.header.e_machine == "EM_RISCV": raise InvalidElfException("Not a RISC-V elf file!") - if not elf.header.e_ident.EI_CLASS == 'ELFCLASS32': + if not elf.header.e_ident.EI_CLASS == "ELFCLASS32": raise InvalidElfException("Only 32bit executables are supported!") from elftools.elf.sections import SymbolTableSection + for sec in elf.iter_sections(): if isinstance(sec, SymbolTableSection): self._parse_symtab(sec) @@ -69,18 +80,22 @@ class ElfBinaryFileLoader(ProgramLoader): self._add_sec(self._lms_from_elf_sec(sec, self.filename)) - def _lms_from_elf_sec(self, sec: 'Section', owner: str): - is_code = sec.name in ('.text',) + def _lms_from_elf_sec(self, sec: "Section", owner: str): + is_code = sec.name in (".text",) data = bytearray(sec.data()) if len(data) < sec.data_size: data += bytearray(len(data) - sec.data_size) flags = MemoryFlags(is_code, is_code) - print(FMT_ELF + "[ElfLoader] Section {} at: {:X}".format(sec.name, sec.header.sh_addr) + FMT_NONE) + print( + FMT_ELF + + "[ElfLoader] Section {} at: {:X}".format(sec.name, sec.header.sh_addr) + + FMT_NONE + ) return ElfMemorySection( data, sec.name, self.program.context, owner, sec.header.sh_addr, flags ) - def _parse_symtab(self, symtab: 'SymbolTableSection'): + def _parse_symtab(self, symtab: "SymbolTableSection"): from elftools.elf.enums import ENUM_ST_VISIBILITY for sym in symtab.iter_symbols(): @@ -88,18 +103,26 @@ class ElfBinaryFileLoader(ProgramLoader): continue self.program.context.labels[sym.name] = sym.entry.st_value # check if it has st_visibility bit set - if sym.entry.st_info.bind == 'STB_GLOBAL': + 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) + print( + FMT_PARSE + + "LOADED GLOBAL SYMBOL {}: {}".format(sym.name, sym.entry.st_value) + + FMT_NONE + ) - def _add_sec(self, new_sec: 'ElfMemorySection'): + def _add_sec(self, new_sec: "ElfMemorySection"): for sec in self.program.sections: if sec.base < sec.end <= new_sec.base or sec.end > sec.base >= new_sec.end: continue else: - print(FMT_ELF + "[ElfLoader] Invalid elf layout: Two sections overlap: \n\t{}\n\t{}".format( - sec, new_sec - ) + FMT_NONE) + print( + FMT_ELF + + "[ElfLoader] Invalid elf layout: Two sections overlap: \n\t{}\n\t{}".format( + sec, new_sec + ) + + FMT_NONE + ) raise RuntimeError("Cannot load elf with overlapping sections!") self.program.add_section(new_sec) diff --git a/riscemu/priv/Exceptions.py b/riscemu/priv/Exceptions.py index 53214df..f9569b4 100644 --- a/riscemu/priv/Exceptions.py +++ b/riscemu/priv/Exceptions.py @@ -45,7 +45,9 @@ class CpuTrap(BaseException): The privilege level this trap targets """ - def __init__(self, code: int, mtval, type: CpuTrapType, priv: PrivModes = PrivModes.MACHINE): + 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 = UInt32(mtval) @@ -63,7 +65,9 @@ class CpuTrap(BaseException): name = "Reserved interrupt({}, {})".format(self.interrupt, self.code) if (self.interrupt, self.code) in MCAUSE_TRANSLATION: - name = MCAUSE_TRANSLATION[(self.interrupt, self.code)] + "({}, {})".format(self.interrupt, self.code) + name = MCAUSE_TRANSLATION[(self.interrupt, self.code)] + "({}, {})".format( + self.interrupt, self.code + ) return "{} {{priv={}, type={}, mtval={:x}}} {}".format( name, self.priv.name, self.type.name, self.mtval, self.message() @@ -74,7 +78,7 @@ class CpuTrap(BaseException): class IllegalInstructionTrap(CpuTrap): - def __init__(self, ins: 'ElfInstruction'): + def __init__(self, ins: "ElfInstruction"): super().__init__(2, ins.encoded, CpuTrapType.EXCEPTION) @@ -104,7 +108,9 @@ class InvalidElfException(RiscemuBaseException): self.msg = msg def message(self): - return FMT_PARSE + "{}(\"{}\")".format(self.__class__.__name__, self.msg) + FMT_NONE + return ( + FMT_PARSE + '{}("{}")'.format(self.__class__.__name__, self.msg) + FMT_NONE + ) class LoadAccessFault(CpuTrap): @@ -117,8 +123,5 @@ class LoadAccessFault(CpuTrap): def message(self): return "(During {} at 0x{:08x} of size {}: {})".format( - self.op, - self.addr, - self.size, - self.msg + self.op, self.addr, self.size, self.msg ) diff --git a/riscemu/priv/ImageLoader.py b/riscemu/priv/ImageLoader.py index de9341a..d513b86 100644 --- a/riscemu/priv/ImageLoader.py +++ b/riscemu/priv/ImageLoader.py @@ -14,10 +14,9 @@ from ..types import MemoryFlags, ProgramLoader, Program, T_ParserOpts class MemoryImageLoader(ProgramLoader): - @classmethod def can_parse(cls, source_path: str) -> float: - if source_path.split('.')[-1] == 'img': + if source_path.split(".")[-1] == "img": return 1 return 0 @@ -26,14 +25,14 @@ class MemoryImageLoader(ProgramLoader): return argv, {} def parse(self) -> Iterable[Program]: - if 'debug' not in self.options: + if "debug" not in self.options: yield self.parse_no_debug() return - with open(self.options.get('debug'), 'r') as debug_file: + with open(self.options.get("debug"), "r") as debug_file: debug_info = MemoryImageDebugInfos.load(debug_file.read()) - with open(self.source_path, 'rb') as source_file: + with open(self.source_path, "rb") as source_file: data: bytearray = bytearray(source_file.read()) for name, sections in debug_info.sections.items(): @@ -43,11 +42,15 @@ 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(False, True) + data[start : start + size], + sec_name, + program.context, + name, + start, + MemoryFlags(False, True), ) ) @@ -57,19 +60,27 @@ class MemoryImageLoader(ProgramLoader): yield program def parse_no_debug(self) -> Program: - print(FMT_PARSE + "[MemoryImageLoader] Warning: loading memory image without debug information!" + FMT_NONE) + print( + FMT_PARSE + + "[MemoryImageLoader] Warning: loading memory image without debug information!" + + FMT_NONE + ) - with open(self.source_path, 'rb') as source_file: + with open(self.source_path, "rb") as source_file: data: bytes = source_file.read() p = Program(self.filename) - p.add_section(ElfMemorySection( - bytearray(data), '.text', p.context, p.name, 0, MemoryFlags(False, True) - )) + p.add_section( + ElfMemorySection( + 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.isfile(source_path + '.dbg'): - return MemoryImageLoader(source_path, dict(**options, debug=source_path + '.dbg')) + def instantiate(cls, source_path: str, options: T_ParserOpts) -> "ProgramLoader": + if os.path.isfile(source_path + ".dbg"): + return MemoryImageLoader( + source_path, dict(**options, debug=source_path + ".dbg") + ) return MemoryImageLoader(source_path, options) diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index e6ec7ef..3c36b27 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -87,7 +87,11 @@ class PrivCPU(CPU): if self.halted: print() - print(FMT_CPU + "[CPU] System halted with code {}".format(self.exit_code) + FMT_NONE) + print( + FMT_CPU + + "[CPU] System halted with code {}".format(self.exit_code) + + FMT_NONE + ) sys.exit(self.exit_code) elif launch_debug: @@ -96,55 +100,68 @@ class PrivCPU(CPU): self.run(verbose) else: print() - print(FMT_CPU + "[CPU] System stopped without halting - perhaps you stopped the debugger?" + FMT_NONE) + print( + FMT_CPU + + "[CPU] System stopped without halting - perhaps you stopped the debugger?" + + FMT_NONE + ) def launch(self, program: Optional[Program] = None, verbose: bool = False): - print(FMT_CPU + '[CPU] Started running from 0x{:08X} ({})'.format(self.pc, "kernel") + FMT_NONE) + print( + FMT_CPU + + "[CPU] Started running from 0x{:08X} ({})".format(self.pc, "kernel") + + FMT_NONE + ) self._time_start = time.perf_counter_ns() // self.TIME_RESOLUTION_NS self.run(self.conf.verbosity > 1 or verbose) def load_program(self, program: Program): - if program.name == 'kernel': + if program.name == "kernel": self.pc = program.entrypoint super().load_program(program) def _init_csr(self): # set up CSR self.csr = CSR() - self.csr.set('mhartid', UInt32(0)) # core id + self.csr.set("mhartid", UInt32(0)) # core id # TODO: set correct value - self.csr.set('mimpid', UInt32(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', UInt32((1 << 30) + (1 << 8) + (1 << 12))) # available ISA + self.csr.set("misa", UInt32((1 << 30) + (1 << 8) + (1 << 12))) # available ISA # CSR write callbacks: - @self.csr.callback('halt') + @self.csr.callback("halt") def halt(old: UInt32, new: UInt32): if new != 0: self.halted = True self.exit_code = new.value - @self.csr.callback('mtimecmp') + @self.csr.callback("mtimecmp") def mtimecmp(old: UInt32, new: UInt32): - self._time_timecmp = (self.csr.get('mtimecmph') << 32) + new + self._time_timecmp = (self.csr.get("mtimecmph") << 32) + new self._time_interrupt_enabled = True - @self.csr.callback('mtimecmph') + @self.csr.callback("mtimecmph") def mtimecmph(old: UInt32, new: UInt32): - self._time_timecmp = (new << 32) + self.csr.get('mtimecmp') + self._time_timecmp = (new << 32) + self.csr.get("mtimecmp") self._time_interrupt_enabled = True # virtual CSR registers: - @self.csr.virtual_register('time') + @self.csr.virtual_register("time") def get_time(): - return UInt32(time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start) + return UInt32( + time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start + ) - @self.csr.virtual_register('timeh') + @self.csr.virtual_register("timeh") def get_timeh(): - return UInt32((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 @@ -160,17 +177,21 @@ class PrivCPU(CPU): self._check_interrupt() ins = self.mmu.read_ins(self.pc) if verbose and (self.mode == PrivModes.USER or self.conf.verbosity > 4): - print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins)) + print( + FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins) + ) self.run_instruction(ins) self.pc += self.INS_XLEN except CpuTrap as trap: self._handle_trap(trap) if trap.interrupt == 0 and not isinstance(trap, EcallTrap): - print(FMT_CPU + "[CPU] Trap {} encountered at {} (0x{:x})".format( - trap, - self.mmu.translate_address(self.pc), - self.pc - ) + FMT_NONE) + print( + FMT_CPU + + "[CPU] Trap {} encountered at {} (0x{:x})".format( + trap, self.mmu.translate_address(self.pc), self.pc + ) + + FMT_NONE + ) breakpoint() if self.conf.debug_on_exception: raise LaunchDebuggerException() @@ -179,12 +200,15 @@ class PrivCPU(CPU): def _timer_step(self): if not self._time_interrupt_enabled: return - if self._time_timecmp <= (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start: + if ( + self._time_timecmp + <= (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start + ): self.pending_traps.append(TimerInterrupt()) self._time_interrupt_enabled = False def _check_interrupt(self): - if not (len(self.pending_traps) > 0 and self.csr.get_mstatus('mie')): + if not (len(self.pending_traps) > 0 and self.csr.get_mstatus("mie")): return # select best interrupt # FIXME: actually select based on the official ranking @@ -194,24 +218,30 @@ class PrivCPU(CPU): self.regs.dump_reg_a() if trap.priv != PrivModes.MACHINE: - print(FMT_CPU + "[CPU] Trap not targeting machine mode encountered! - undefined behaviour!" + FMT_NONE) + print( + FMT_CPU + + "[CPU] Trap not targeting machine mode encountered! - undefined behaviour!" + + FMT_NONE + ) raise Exception("Undefined behaviour!") if self.mode != PrivModes.USER: print(FMT_CPU + "[CPU] Trap triggered outside of user mode?!" + FMT_NONE) - self.csr.set_mstatus('mpie', self.csr.get_mstatus('mie')) - self.csr.set_mstatus('mpp', self.mode.value) - 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.csr.set_mstatus("mpie", self.csr.get_mstatus("mie")) + self.csr.set_mstatus("mpp", self.mode.value) + 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') + mtvec = self.csr.get("mtvec") if mtvec & 0b11 == 0: self.pc = mtvec.value if mtvec & 0b11 == 1: - self.pc = ((mtvec & 0b11111111111111111111111111111100) + (trap.code * 4)).value + self.pc = ( + (mtvec & 0b11111111111111111111111111111100) + (trap.code * 4) + ).value self.record_perf_profile() if len(self._perf_counters) > 100: self.show_perf() @@ -232,7 +262,10 @@ class PrivCPU(CPU): cycled = cycle timed = time_ns cps_list.append(cps) - print(" on average {:.0f} instructions/s".format(sum(cps_list) / len(cps_list)) + FMT_NONE) + print( + " on average {:.0f} instructions/s".format(sum(cps_list) / len(cps_list)) + + FMT_NONE + ) self._perf_counters = list() def record_perf_profile(self): @@ -240,6 +273,4 @@ class PrivCPU(CPU): @classmethod def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]: - return [ - AssemblyFileLoader, MemoryImageLoader, ElfBinaryFileLoader - ] + return [AssemblyFileLoader, MemoryImageLoader, ElfBinaryFileLoader] diff --git a/riscemu/priv/PrivMMU.py b/riscemu/priv/PrivMMU.py index 1504ab2..16f54d8 100644 --- a/riscemu/priv/PrivMMU.py +++ b/riscemu/priv/PrivMMU.py @@ -9,7 +9,6 @@ if typing.TYPE_CHECKING: class PrivMMU(MMU): - def get_sec_containing(self, addr: T_AbsoluteAddress) -> MemorySection: # try to get an existing section existing_sec = super().get_sec_containing(addr) @@ -18,7 +17,9 @@ class PrivMMU(MMU): return existing_sec # get section preceding empty space at addr - sec_before = next((sec for sec in reversed(self.sections) if sec.end < addr), None) + sec_before = next( + (sec for sec in reversed(self.sections) if sec.end < addr), None + ) # get sec succeeding empty space at addr sec_after = next((sec for sec in self.sections if sec.base > addr), None) @@ -31,7 +32,14 @@ class PrivMMU(MMU): # end at the start of the next section, or address + 0xFFFF (aligned to 16 byte boundary) end = min(next_sec_start, align_addr(addr + 0xFFFF, 16)) - sec = ElfMemorySection(bytearray(end - start), '.empty', self.global_instruction_context(), '', start, MemoryFlags(False, True)) + sec = ElfMemorySection( + bytearray(end - start), + ".empty", + self.global_instruction_context(), + "", + start, + MemoryFlags(False, True), + ) self.sections.append(sec) self._update_state() @@ -40,4 +48,4 @@ class PrivMMU(MMU): def global_instruction_context(self) -> InstructionContext: context = InstructionContext() context.global_symbol_dict = self.global_symbols - return context \ No newline at end of file + return context diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index 132c7fd..9f791d2 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -16,155 +16,159 @@ if typing.TYPE_CHECKING: class PrivRV32I(RV32I): - cpu: 'PrivCPU' + cpu: "PrivCPU" """ This is an extension of RV32I, written for the PrivCPU class """ - def instruction_csrrw(self, ins: 'Instruction'): + def instruction_csrrw(self, ins: "Instruction"): rd, rs, csr_addr = self.parse_crs_ins(ins) old_val = None - if rd != 'zero': + if rd != "zero": self.cpu.csr.assert_can_read(self.cpu.mode, csr_addr) old_val = self.cpu.csr.get(csr_addr) - if rs != 'zero': + if rs != "zero": new_val = self.regs.get(rs) self.cpu.csr.assert_can_write(self.cpu.mode, csr_addr) self.cpu.csr.set(csr_addr, new_val) if old_val is not None: self.regs.set(rd, old_val) - def instruction_csrrs(self, ins: 'Instruction'): + def instruction_csrrs(self, ins: "Instruction"): rd, rs, csr_addr = self.parse_crs_ins(ins) - if rs != 'zero': + if rs != "zero": # oh no, this should not happen! INS_NOT_IMPLEMENTED(ins) - if rd != 'zero': + if rd != "zero": self.cpu.csr.assert_can_read(self.cpu.mode, csr_addr) old_val = self.cpu.csr.get(csr_addr) self.regs.set(rd, old_val) - def instruction_csrrc(self, ins: 'Instruction'): + def instruction_csrrc(self, ins: "Instruction"): INS_NOT_IMPLEMENTED(ins) - def instruction_csrrsi(self, ins: 'Instruction'): + def instruction_csrrsi(self, ins: "Instruction"): INS_NOT_IMPLEMENTED(ins) - def instruction_csrrwi(self, ins: 'Instruction'): + def instruction_csrrwi(self, ins: "Instruction"): ASSERT_LEN(ins.args, 3) rd, imm, addr = ins.get_reg(0), ins.get_imm(1), ins.get_imm(2) - if rd != 'zero': + if rd != "zero": self.cpu.csr.assert_can_read(self.cpu.mode, addr) old_val = self.cpu.csr.get(addr) self.regs.set(rd, old_val) self.cpu.csr.assert_can_write(self.cpu.mode, addr) self.cpu.csr.set(addr, imm) - def instruction_csrrci(self, ins: 'Instruction'): + def instruction_csrrci(self, ins: "Instruction"): INS_NOT_IMPLEMENTED(ins) - def instruction_mret(self, ins: 'Instruction'): + def instruction_mret(self, ins: "Instruction"): if self.cpu.mode != PrivModes.MACHINE: print("MRET not inside machine level code!") raise IllegalInstructionTrap(ins) # retore mie - mpie = self.cpu.csr.get_mstatus('mpie') - self.cpu.csr.set_mstatus('mie', mpie) + mpie = self.cpu.csr.get_mstatus("mpie") + self.cpu.csr.set_mstatus("mie", mpie) # restore priv - mpp = self.cpu.csr.get_mstatus('mpp') + mpp = self.cpu.csr.get_mstatus("mpp") self.cpu.mode = PrivModes(mpp) # restore pc - mepc = self.cpu.csr.get('mepc') + mepc = self.cpu.csr.get("mepc") self.cpu.pc = (mepc - self.cpu.INS_XLEN).value if self.cpu.conf.verbosity > 0: 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, - self.mmu.translate_address(mepc), - mepc - ) + FMT_NONE) + print( + FMT_CPU + + "[CPU] returning to mode {} in {} (0x{:x})".format( + PrivModes(mpp).name, self.mmu.translate_address(mepc), mepc + ) + + FMT_NONE + ) if self.cpu.conf.verbosity > 1: self.regs.dump_reg_a() - def instruction_uret(self, ins: 'Instruction'): + def instruction_uret(self, ins: "Instruction"): raise IllegalInstructionTrap(ins) - def instruction_sret(self, ins: 'Instruction'): + def instruction_sret(self, ins: "Instruction"): raise IllegalInstructionTrap(ins) - def instruction_scall(self, ins: 'Instruction'): + def instruction_scall(self, ins: "Instruction"): """ Overwrite the scall from userspace RV32I """ raise EcallTrap(self.cpu.mode) - def instruction_beq(self, ins: 'Instruction'): + def instruction_beq(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 == rs2: self.pc += dst.value - 4 - def instruction_bne(self, ins: 'Instruction'): + def instruction_bne(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 != rs2: self.pc += dst.value - 4 - def instruction_blt(self, ins: 'Instruction'): + def instruction_blt(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 < rs2: self.pc += dst.value - 4 - def instruction_bge(self, ins: 'Instruction'): + def instruction_bge(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 >= rs2: self.pc += dst.value - 4 - def instruction_bltu(self, ins: 'Instruction'): + def instruction_bltu(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) if rs1 < rs2: self.pc += dst.value - 4 - def instruction_bgeu(self, ins: 'Instruction'): + def instruction_bgeu(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) if rs1 >= rs2: self.pc += dst.value - 4 # technically deprecated - def instruction_j(self, ins: 'Instruction'): + def instruction_j(self, ins: "Instruction"): raise NotImplementedError("Should never be reached!") - def instruction_jal(self, ins: 'Instruction'): + def instruction_jal(self, ins: "Instruction"): 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) or - (self.cpu.conf.verbosity > 3) + 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) + 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.dump_reg_a() self.regs.set(reg, Int32(self.pc)) self.pc += addr - 4 - def instruction_jalr(self, ins: 'Instruction'): + def instruction_jalr(self, ins: "Instruction"): ASSERT_LEN(ins.args, 3) rd, rs, imm = self.parse_rd_rs_imm(ins) self.regs.set(rd, Int32(self.pc)) self.pc = rs.value + imm.value - 4 - def instruction_sbreak(self, ins: 'Instruction'): + def instruction_sbreak(self, ins: "Instruction"): raise LaunchDebuggerException() - def parse_crs_ins(self, ins: 'Instruction'): + def parse_crs_ins(self, ins: "Instruction"): ASSERT_LEN(ins.args, 3) return ins.get_reg(0), ins.get_reg(1), ins.get_imm(2) - def parse_mem_ins(self, ins: 'Instruction') -> Tuple[str, int]: + def parse_mem_ins(self, ins: "Instruction") -> Tuple[str, int]: ASSERT_LEN(ins.args, 3) addr = self.get_reg_content(ins, 1) + ins.get_imm(2) reg = ins.get_reg(0) diff --git a/riscemu/priv/__init__.py b/riscemu/priv/__init__.py index 26e488f..341a9d4 100644 --- a/riscemu/priv/__init__.py +++ b/riscemu/priv/__init__.py @@ -12,4 +12,4 @@ Syscalls will have to be intercepted by your assembly code. The PrivCPU Implements the Risc-V M/U Model, meaning there is machine mode and user mode. No PMP or paging is available. -""" \ No newline at end of file +""" diff --git a/riscemu/priv/__main__.py b/riscemu/priv/__main__.py index 0f869da..aac301b 100644 --- a/riscemu/priv/__main__.py +++ b/riscemu/priv/__main__.py @@ -4,27 +4,55 @@ from .PrivCPU import PrivCPU import sys -if __name__ == '__main__': +if __name__ == "__main__": import argparse - parser = argparse.ArgumentParser(description='RISC-V privileged architecture emulator', prog='riscemu') + parser = argparse.ArgumentParser( + description="RISC-V privileged architecture emulator", prog="riscemu" + ) - parser.add_argument('source', type=str, - help='Compiled RISC-V ELF file or memory image containing compiled RISC-V ELF files', nargs='+') - parser.add_argument('--debug-exceptions', help='Launch the interactive debugger when an exception is generated', - action='store_true') + parser.add_argument( + "source", + type=str, + help="Compiled RISC-V ELF file or memory image containing compiled RISC-V ELF files", + nargs="+", + ) + parser.add_argument( + "--debug-exceptions", + help="Launch the interactive debugger when an exception is generated", + action="store_true", + ) - parser.add_argument('-v', '--verbose', help="Verbosity level (can be used multiple times)", action='count', - default=0) + parser.add_argument( + "-v", + "--verbose", + help="Verbosity level (can be used multiple times)", + action="count", + default=0, + ) - parser.add_argument('--slowdown', help="Slow down the emulated CPU clock by a factor", type=float, default=1) + parser.add_argument( + "--slowdown", + help="Slow down the emulated CPU clock by a factor", + type=float, + default=1, + ) args = parser.parse_args() - cpu = PrivCPU(RunConfig(verbosity=args.verbose, debug_on_exception=args.debug_exceptions, slowdown=args.slowdown)) + cpu = PrivCPU( + RunConfig( + verbosity=args.verbose, + debug_on_exception=args.debug_exceptions, + slowdown=args.slowdown, + ) + ) for source_path in args.source: - loader = max((loader for loader in cpu.get_loaders()), key=lambda l: l.can_parse(source_path)) + loader = max( + (loader for loader in cpu.get_loaders()), + key=lambda l: l.can_parse(source_path), + ) argv, opts = loader.get_options(sys.argv) program = loader.instantiate(source_path, opts).parse() if isinstance(program, Program): diff --git a/riscemu/priv/types.py b/riscemu/priv/types.py index 7882bdd..597744e 100644 --- a/riscemu/priv/types.py +++ b/riscemu/priv/types.py @@ -6,9 +6,19 @@ from typing import Tuple, Dict, Set from riscemu.colors import FMT_NONE, FMT_PARSE from riscemu.decoder import format_ins, RISCV_REGS, decode -from riscemu.priv.Exceptions import InstructionAccessFault, InstructionAddressMisalignedTrap, LoadAccessFault -from riscemu.types import Instruction, InstructionContext, T_RelativeAddress, MemoryFlags, T_AbsoluteAddress, \ - BinaryDataMemorySection +from riscemu.priv.Exceptions import ( + InstructionAccessFault, + InstructionAddressMisalignedTrap, + LoadAccessFault, +) +from riscemu.types import ( + Instruction, + InstructionContext, + T_RelativeAddress, + MemoryFlags, + T_AbsoluteAddress, + BinaryDataMemorySection, +) @dataclass(frozen=True) @@ -27,34 +37,45 @@ class ElfInstruction(Instruction): return RISCV_REGS[self.args[num]] def __repr__(self) -> str: - if self.name == 'jal' and self.args[0] == 0: + if self.name == "jal" and self.args[0] == 0: return "j {}".format(self.args[1]) - if self.name == 'addi' and self.args[2] == 0: + if self.name == "addi" and self.args[2] == 0: return "mv {}, {}".format(self.get_reg(0), self.get_reg(1)) - if self.name == 'addi' and self.args[1] == 0: + if self.name == "addi" and self.args[1] == 0: return "li {}, {}".format(self.get_reg(0), self.args[2]) - if self.name == 'ret' and len(self.args) == 0: + if self.name == "ret" and len(self.args) == 0: return "ret" return format_ins(self.encoded, self.name) class ElfMemorySection(BinaryDataMemorySection): - def __init__(self, data: bytearray, name: str, context: InstructionContext, owner: str, base: int, - flags: MemoryFlags): + 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) def read_ins(self, offset): if not self.flags.executable: - print(FMT_PARSE + "Reading instruction from non-executable memory!" + FMT_NONE) + print( + FMT_PARSE + "Reading instruction from non-executable memory!" + FMT_NONE + ) raise InstructionAccessFault(offset + self.base) if offset % 4 != 0: raise InstructionAddressMisalignedTrap(offset + self.base) - return ElfInstruction(*decode(self.data[offset:offset + 4])) + return ElfInstruction(*decode(self.data[offset : offset + 4])) def write(self, offset: T_RelativeAddress, size: int, data: bytearray): if self.flags.read_only: - raise LoadAccessFault('read-only section', offset + self.base, size, 'write') + raise LoadAccessFault( + "read-only section", offset + self.base, size, "write" + ) self.read_ins.cache_clear() return super(ElfMemorySection, self).write(offset, size, data) @@ -64,7 +85,7 @@ class ElfMemorySection(BinaryDataMemorySection): class MemoryImageDebugInfos: - VERSION = '1.0.0' + VERSION = "1.0.0" """ Schema version """ @@ -89,12 +110,13 @@ class MemoryImageDebugInfos: This dictionary contains the list of all global symbols of a given program """ - def __init__(self, - sections: Dict[str, Dict[str, Tuple[int, int]]], - symbols: Dict[str, Dict[str, int]], - globals: Dict[str, Set[str]], - base: int = 0 - ): + def __init__( + self, + sections: Dict[str, Dict[str, Tuple[int, int]]], + symbols: Dict[str, Dict[str, int]], + globals: Dict[str, Set[str]], + base: int = 0, + ): self.sections = sections self.symbols = symbols self.globals = globals @@ -108,7 +130,9 @@ class MemoryImageDebugInfos: return json.dumps(dict(obj), default=serialize) if isinstance(obj, (set, tuple)): return json.dumps(list(obj), default=serialize) - return "<>".format(getattr(obj, '__qualname__', '{unknown}')) + return "<>".format( + getattr(obj, "__qualname__", "{unknown}") + ) return json.dumps( dict( @@ -116,22 +140,25 @@ class MemoryImageDebugInfos: symbols=self.symbols, globals=self.globals, base=self.base, - VERSION=self.VERSION + VERSION=self.VERSION, ), - default=serialize + default=serialize, ) @classmethod - def load(cls, serialized_str: str) -> 'MemoryImageDebugInfos': + def load(cls, serialized_str: str) -> "MemoryImageDebugInfos": json_obj: dict = json.loads(serialized_str) - if 'VERSION' not in json_obj: + if "VERSION" not in json_obj: raise RuntimeError("Unknown MemoryImageDebugInfo version!") - version: str = json_obj.pop('VERSION') + version: str = json_obj.pop("VERSION") # compare major version - if version != cls.VERSION and 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 @@ -141,7 +168,7 @@ class MemoryImageDebugInfos: return MemoryImageDebugInfos(**json_obj) @classmethod - def builder(cls) -> 'MemoryImageDebugInfos': + def builder(cls) -> "MemoryImageDebugInfos": return MemoryImageDebugInfos( defaultdict(dict), defaultdict(dict), defaultdict(set) ) diff --git a/riscemu/registers.py b/riscemu/registers.py index 80ebf6c..c6150e6 100644 --- a/riscemu/registers.py +++ b/riscemu/registers.py @@ -18,17 +18,72 @@ class Registers: """ valid_regs = { - 'zero', 'ra', 'sp', 'gp', 'tp', 's0', 'fp', - 't0', 't1', 't2', 't3', 't4', 't5', 't6', - 's1', 's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11', - 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', - 'ft0', 'ft1', 'ft2', 'ft3', 'ft4', 'ft5', 'ft6', 'ft7', - 'fs0', 'fs1', 'fs2', 'fs3', 'fs4', 'fs5', 'fs6', 'fs7', 'fs8', 'fs9', 'fs10', 'fs11', - 'fa0', 'fa1', 'fa2', 'fa3', 'fa4', 'fa5', 'fa6', 'fa7' + "zero", + "ra", + "sp", + "gp", + "tp", + "s0", + "fp", + "t0", + "t1", + "t2", + "t3", + "t4", + "t5", + "t6", + "s1", + "s2", + "s3", + "s4", + "s5", + "s6", + "s7", + "s8", + "s9", + "s10", + "s11", + "a0", + "a1", + "a2", + "a3", + "a4", + "a5", + "a6", + "a7", + "ft0", + "ft1", + "ft2", + "ft3", + "ft4", + "ft5", + "ft6", + "ft7", + "fs0", + "fs1", + "fs2", + "fs3", + "fs4", + "fs5", + "fs6", + "fs7", + "fs8", + "fs9", + "fs10", + "fs11", + "fa0", + "fa1", + "fa2", + "fa3", + "fa4", + "fa5", + "fa6", + "fa7", } def __init__(self, infinite_regs: bool = False): from .types import Int32 + self.vals = defaultdict(lambda: Int32(0)) self.last_set = None self.last_read = None @@ -43,28 +98,30 @@ class Registers: lines = [[] for i in range(12)] if not full: - regs = [('a', 8), ('s', 12), ('t', 7)] + regs = [("a", 8), ("s", 12), ("t", 7)] else: regs = [ - ('a', 8), - ('s', 12), - ('t', 7), - ('ft', 8), - ('fa', 8), - ('fs', 12), + ("a", 8), + ("s", 12), + ("t", 7), + ("ft", 8), + ("fa", 8), + ("fs", 12), ] for name, s in regs: for i in range(12): if i >= s: lines[i].append(" " * 15) else: - reg = '{}{}'.format(name, i) + reg = "{}{}".format(name, i) lines[i].append(self._reg_repr(reg)) - print("Registers[{},{}](".format( - FMT_ORANGE + FMT_UNDERLINE + 'read' + FMT_NONE, - FMT_ORANGE + FMT_BOLD + 'written' + FMT_NONE - )) + print( + "Registers[{},{}](".format( + FMT_ORANGE + FMT_UNDERLINE + "read" + FMT_NONE, + FMT_ORANGE + FMT_BOLD + "written" + FMT_NONE, + ) + ) if not full: print("\t" + " ".join(named_regs[0:3])) print("\t" + " ".join(named_regs[3:])) @@ -80,23 +137,26 @@ class Registers: """ Dump the a registers """ - print("Registers[a]:" + " ".join(self._reg_repr('a{}'.format(i)) for i in range(8))) + print( + "Registers[a]:" + + " ".join(self._reg_repr("a{}".format(i)) for i in range(8)) + ) def _reg_repr(self, reg): - txt = '{:4}=0x{:08X}'.format(reg, self.get(reg, False)) - if reg == 'fp': - reg = 's0' + txt = "{:4}=0x{:08X}".format(reg, self.get(reg, False)) + if reg == "fp": + reg = "s0" if reg == self.last_set: return FMT_ORANGE + FMT_BOLD + txt + FMT_NONE if reg == self.last_read: return FMT_ORANGE + FMT_UNDERLINE + txt + FMT_NONE - if reg == 'zero': + if reg == "zero": return txt if self.get(reg, False) == 0 and reg not in Registers.named_registers(): return FMT_GRAY + txt + FMT_NONE return txt - def set(self, reg, val: 'Int32', 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 @@ -106,17 +166,20 @@ class Registers: """ 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!") + raise RuntimeError( + "Setting register to non-Int32 value! Please refactor your code!" + ) - if reg == 'zero': + if reg == "zero": return False # if reg not in Registers.all_registers(): # raise InvalidRegisterException(reg) # replace fp register with s1, as these are the same register - if reg == 'fp': - reg = 's1' + if reg == "fp": + reg = "s1" if mark_set: self.last_set = reg @@ -126,7 +189,7 @@ class Registers: self.vals[reg] = val.unsigned() return True - def get(self, reg, mark_read=True) -> 'Int32': + def get(self, reg, mark_read=True) -> "Int32": """ Retuns the contents of register reg :param reg: The register name @@ -135,8 +198,8 @@ class Registers: """ # if reg not in Registers.all_registers(): # raise InvalidRegisterException(reg) - if reg == 'fp': - reg = 's0' + if reg == "fp": + reg = "s0" if not self.infinite_regs and reg not in self.valid_regs: raise RuntimeError("Invalid register: {}".format(reg)) @@ -151,13 +214,69 @@ class Registers: Return a list of all valid registers :return: The list """ - return ['zero', 'ra', 'sp', 'gp', 'tp', 's0', 'fp', - 't0', 't1', 't2', 't3', 't4', 't5', 't6', - 's1', 's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11', - 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', - 'ft0', 'ft1', 'ft2', 'ft3', 'ft4', 'ft5', 'ft6', 'ft7', - 'fs0', 'fs1', 'fs2', 'fs3', 'fs4', 'fs5', 'fs6', 'fs7', 'fs8', 'fs9', 'fs10', 'fs11', - 'fa0', 'fa1', 'fa2', 'fa3', 'fa4', 'fa5', 'fa6', 'fa7'] + return [ + "zero", + "ra", + "sp", + "gp", + "tp", + "s0", + "fp", + "t0", + "t1", + "t2", + "t3", + "t4", + "t5", + "t6", + "s1", + "s2", + "s3", + "s4", + "s5", + "s6", + "s7", + "s8", + "s9", + "s10", + "s11", + "a0", + "a1", + "a2", + "a3", + "a4", + "a5", + "a6", + "a7", + "ft0", + "ft1", + "ft2", + "ft3", + "ft4", + "ft5", + "ft6", + "ft7", + "fs0", + "fs1", + "fs2", + "fs3", + "fs4", + "fs5", + "fs6", + "fs7", + "fs8", + "fs9", + "fs10", + "fs11", + "fa0", + "fa1", + "fa2", + "fa3", + "fa4", + "fa5", + "fa6", + "fa7", + ] @staticmethod def named_registers(): @@ -165,4 +284,4 @@ class Registers: Return all named registers :return: The list """ - return ['zero', 'ra', 'sp', 'gp', 'tp', 'fp'] + return ["zero", "ra", "sp", "gp", "tp", "fp"] diff --git a/riscemu/syscall.py b/riscemu/syscall.py index 7ac95d6..9401c5f 100644 --- a/riscemu/syscall.py +++ b/riscemu/syscall.py @@ -13,11 +13,11 @@ from .types import Int32, CPU from .types.exceptions import InvalidSyscallException SYSCALLS = { - 63: 'read', - 64: 'write', - 93: 'exit', - 1024: 'open', - 1025: 'close', + 63: "read", + 64: "write", + 93: "exit", + 1024: "open", + 1025: "close", } """ This dict contains a mapping for all available syscalls (code->name) @@ -28,11 +28,11 @@ class. """ OPEN_MODES = { - 0: 'rb', - 1: 'wb', - 2: 'r+b', - 3: 'x', - 4: 'ab', + 0: "rb", + 1: "wb", + 2: "r+b", + 3: "x", + 4: "ab", } """All available file open modes""" @@ -42,6 +42,7 @@ class Syscall: """ Represents a syscall """ + id: int """The syscall number (e.g. 64 - write)""" cpu: CPU @@ -52,12 +53,10 @@ class Syscall: return SYSCALLS.get(self.id, "unknown") def __repr__(self): - return "Syscall(id={}, name={})".format( - self.id, self.name - ) + return "Syscall(id={}, name={})".format(self.id, self.name) def ret(self, code): - self.cpu.regs.set('a0', Int32(code)) + self.cpu.regs.set("a0", Int32(code)) def get_syscall_symbols(): @@ -66,25 +65,20 @@ def get_syscall_symbols(): :return: dictionary of all syscall symbols (SCALL_ -> id) """ - return { - ('SCALL_' + name.upper()): num for num, name in SYSCALLS.items() - } + return {("SCALL_" + name.upper()): num for num, name in SYSCALLS.items()} class SyscallInterface: """ Handles syscalls """ + open_files: Dict[int, IO] next_open_handle: int def handle_syscall(self, scall: Syscall): self.next_open_handle = 3 - self.open_files = { - 0: sys.stdin, - 1: sys.stdout, - 2: sys.stderr - } + self.open_files = {0: sys.stdin, 1: sys.stdout, 2: sys.stderr} if getattr(self, scall.name): getattr(self, scall.name)(scall) @@ -96,21 +90,25 @@ class SyscallInterface: read syscall (63): read from file no a0, into addr a1, at most a2 bytes on return a0 will be the number of read bytes or -1 if an error occured """ - fileno = scall.cpu.regs.get('a0').unsigned_value - addr = scall.cpu.regs.get('a1').unsigned_value - size = scall.cpu.regs.get('a2').unsigned_value + fileno = scall.cpu.regs.get("a0").unsigned_value + addr = scall.cpu.regs.get("a1").unsigned_value + size = scall.cpu.regs.get("a2").unsigned_value if fileno not in self.open_files: scall.ret(-1) return chars = self.open_files[fileno].readline(size) try: - data = bytearray(chars, 'ascii') + data = bytearray(chars, "ascii") scall.cpu.mmu.write(addr, len(data), data) return scall.ret(len(data)) except UnicodeEncodeError: - print(FMT_SYSCALL + '[Syscall] read: UnicodeError - invalid input "{}"'.format(chars) + FMT_NONE) + print( + FMT_SYSCALL + + '[Syscall] read: UnicodeError - invalid input "{}"'.format(chars) + + FMT_NONE + ) return scall.ret(-1) def write(self, scall: Syscall): @@ -118,19 +116,23 @@ class SyscallInterface: write syscall (64): write a2 bytes from addr a1 into fileno a0 on return a0 will hold the number of bytes written or -1 if an error occured """ - fileno = scall.cpu.regs.get('a0').unsigned_value - addr = scall.cpu.regs.get('a1').unsigned_value - size = scall.cpu.regs.get('a2').unsigned_value + fileno = scall.cpu.regs.get("a0").unsigned_value + addr = scall.cpu.regs.get("a1").unsigned_value + size = scall.cpu.regs.get("a2").unsigned_value if fileno not in self.open_files: return scall.ret(-1) data = scall.cpu.mmu.read(addr, size) if not isinstance(data, bytearray): - print(FMT_SYSCALL + '[Syscall] write: writing from .text region not supported.' + FMT_NONE) + print( + FMT_SYSCALL + + "[Syscall] write: writing from .text region not supported." + + FMT_NONE + ) return scall.ret(-1) - self.open_files[fileno].write(data.decode('ascii')) + self.open_files[fileno].write(data.decode("ascii")) return scall.ret(size) def open(self, scall: Syscall): @@ -149,19 +151,29 @@ class SyscallInterface: """ # FIXME: this should be toggleable in a global setting or something if True: - print(FMT_SYSCALL + '[Syscall] open: opening files not supported without scall-fs flag!' + FMT_NONE) + print( + FMT_SYSCALL + + "[Syscall] open: opening files not supported without scall-fs flag!" + + FMT_NONE + ) return scall.ret(-1) - mode = scall.cpu.regs.get('a0').unsigned_value - addr = scall.cpu.regs.get('a1').unsigned_value - size = scall.cpu.regs.get('a2').unsigned_value + mode = scall.cpu.regs.get("a0").unsigned_value + addr = scall.cpu.regs.get("a1").unsigned_value + size = scall.cpu.regs.get("a2").unsigned_value - mode_st = OPEN_MODES.get(mode, ) + mode_st = OPEN_MODES.get( + mode, + ) if mode_st == -1: - print(FMT_SYSCALL + '[Syscall] open: unknown opening mode {}!'.format(mode) + FMT_NONE) + print( + FMT_SYSCALL + + "[Syscall] open: unknown opening mode {}!".format(mode) + + FMT_NONE + ) return scall.ret(-1) - path = scall.cpu.mmu.read(addr, size).decode('ascii') + path = scall.cpu.mmu.read(addr, size).decode("ascii") fileno = self.next_open_handle self.next_open_handle += 1 @@ -169,10 +181,18 @@ class SyscallInterface: try: self.open_files[fileno] = open(path, mode_st) except OSError as err: - print(FMT_SYSCALL + '[Syscall] open: encountered error during {}!'.format(err.strerror) + FMT_NONE) + print( + FMT_SYSCALL + + "[Syscall] open: encountered error during {}!".format(err.strerror) + + FMT_NONE + ) return scall.ret(-1) - print(FMT_SYSCALL + '[Syscall] open: opened fd {} to {}!'.format(fileno, path) + FMT_NONE) + print( + FMT_SYSCALL + + "[Syscall] open: opened fd {} to {}!".format(fileno, path) + + FMT_NONE + ) return scall.ret(fileno) def close(self, scall: Syscall): @@ -181,13 +201,17 @@ class SyscallInterface: return -1 if an error was encountered, otherwise returns 0 """ - fileno = scall.cpu.regs.get('a0').unsigned_value + fileno = scall.cpu.regs.get("a0").unsigned_value if fileno not in self.open_files: - print(FMT_SYSCALL + '[Syscall] close: unknown fileno {}!'.format(fileno) + FMT_NONE) + print( + FMT_SYSCALL + + "[Syscall] close: unknown fileno {}!".format(fileno) + + FMT_NONE + ) return scall.ret(-1) self.open_files[fileno].close() - print(FMT_SYSCALL + '[Syscall] close: closed fd {}!'.format(fileno) + FMT_NONE) + print(FMT_SYSCALL + "[Syscall] close: closed fd {}!".format(fileno) + FMT_NONE) del self.open_files[fileno] return scall.ret(0) @@ -196,10 +220,7 @@ class SyscallInterface: Exit syscall. Exits the system with status code a0 """ scall.cpu.halted = True - scall.cpu.exit_code = scall.cpu.regs.get('a0').value + scall.cpu.exit_code = scall.cpu.regs.get("a0").value def __repr__(self): - return "{}(\n\tfiles={}\n)".format( - self.__class__.__name__, - self.open_files - ) + return "{}(\n\tfiles={}\n)".format(self.__class__.__name__, self.open_files) diff --git a/riscemu/tokenizer.py b/riscemu/tokenizer.py index 18fa898..ca8ade7 100644 --- a/riscemu/tokenizer.py +++ b/riscemu/tokenizer.py @@ -12,9 +12,11 @@ from typing import List, Iterable from riscemu.decoder import RISCV_REGS from riscemu.types.exceptions import ParseException -LINE_COMMENT_STARTERS = ('#', ';', '//') -WHITESPACE_PATTERN = re.compile(r'\s+') -MEMORY_ADDRESS_PATTERN = re.compile(r'^(0[xX][A-f0-9]+|\d+|0b[0-1]+|[A-z0-9_-]+)\(([A-z]+[0-9]{0,2})\)$') +LINE_COMMENT_STARTERS = ("#", ";", "//") +WHITESPACE_PATTERN = re.compile(r"\s+") +MEMORY_ADDRESS_PATTERN = re.compile( + r"^(0[xX][A-f0-9]+|\d+|0b[0-1]+|[A-z0-9_-]+)\(([A-z]+[0-9]{0,2})\)$" +) REGISTER_NAMES = RISCV_REGS @@ -34,22 +36,22 @@ class Token: def __str__(self): if self.type == TokenType.NEWLINE: - return '\\n' + return "\\n" if self.type == TokenType.COMMA: - return ', ' - return '{}({})'.format(self.type.name[0:3], self.value) + return ", " + return "{}({})".format(self.type.name[0:3], self.value) -NEWLINE = Token(TokenType.NEWLINE, '\n') -COMMA = Token(TokenType.COMMA, ',') +NEWLINE = Token(TokenType.NEWLINE, "\n") +COMMA = Token(TokenType.COMMA, ",") def tokenize(input: Iterable[str]) -> Iterable[Token]: for line in input: for line_comment_start in LINE_COMMENT_STARTERS: if line_comment_start in line: - line = line[:line.index(line_comment_start)] - line.strip(' \t\n') + line = line[: line.index(line_comment_start)] + line.strip(" \t\n") if not line: continue @@ -64,9 +66,9 @@ def parse_line(parts: List[str]) -> Iterable[Token]: return () first_token = parts[0] - if first_token[0] == '.': + if first_token[0] == ".": yield Token(TokenType.PSEUDO_OP, first_token) - elif first_token[-1] == ':': + elif first_token[-1] == ":": yield Token(TokenType.LABEL, first_token) yield from parse_line(parts[1:]) return @@ -74,14 +76,14 @@ def parse_line(parts: List[str]) -> Iterable[Token]: yield Token(TokenType.INSTRUCTION_NAME, first_token) for part in parts[1:]: - if part == ',': + if part == ",": yield COMMA continue yield from parse_arg(part) def parse_arg(arg: str) -> Iterable[Token]: - comma = arg[-1] == ',' + comma = arg[-1] == "," arg = arg[:-1] if comma else arg mem_match_resul = re.match(MEMORY_ADDRESS_PATTERN, arg) if mem_match_resul: @@ -98,7 +100,7 @@ def parse_arg(arg: str) -> Iterable[Token]: def print_tokens(tokens: Iterable[Token]): for token in tokens: - print(token, end='\n' if token == NEWLINE else '') + print(token, end="\n" if token == NEWLINE else "") print("", flush=True, end="") @@ -123,7 +125,7 @@ def split_whitespace_respecting_quotes(line: str) -> Iterable[str]: part = "" continue - if c in ' \t\n': + if c in " \t\n": if part: yield part part = "" diff --git a/riscemu/types/__init__.py b/riscemu/types/__init__.py index d703372..56705dc 100644 --- a/riscemu/types/__init__.py +++ b/riscemu/types/__init__.py @@ -8,7 +8,7 @@ T_AbsoluteAddress = int # parser options are just dictionaries with arbitrary values T_ParserOpts = Dict[str, any] -NUMBER_SYMBOL_PATTERN = re.compile(r'^\d+[fb]$') +NUMBER_SYMBOL_PATTERN = re.compile(r"^\d+[fb]$") # base classes from .flags import MemoryFlags @@ -24,6 +24,16 @@ from .instruction_memory_section import InstructionMemorySection from .binary_data_memory_section import BinaryDataMemorySection # exceptions -from .exceptions import ParseException, NumberFormatException, MemoryAccessException, OutOfMemoryException, \ - LinkerException, LaunchDebuggerException, RiscemuBaseException, InvalidRegisterException, \ - InvalidAllocationException, InvalidSyscallException, UnimplementedInstruction +from .exceptions import ( + ParseException, + NumberFormatException, + MemoryAccessException, + OutOfMemoryException, + LinkerException, + LaunchDebuggerException, + RiscemuBaseException, + InvalidRegisterException, + InvalidAllocationException, + InvalidSyscallException, + UnimplementedInstruction, +) diff --git a/riscemu/types/binary_data_memory_section.py b/riscemu/types/binary_data_memory_section.py index 86bdd77..07d32e1 100644 --- a/riscemu/types/binary_data_memory_section.py +++ b/riscemu/types/binary_data_memory_section.py @@ -1,9 +1,23 @@ -from . import MemorySection, InstructionContext, MemoryFlags, T_RelativeAddress, Instruction +from . import ( + MemorySection, + InstructionContext, + MemoryFlags, + T_RelativeAddress, + Instruction, +) from ..types.exceptions import MemoryAccessException class BinaryDataMemorySection(MemorySection): - def __init__(self, data: bytearray, name: str, context: InstructionContext, owner: str, base: int = 0, flags: MemoryFlags = None): + def __init__( + self, + data: bytearray, + name: str, + context: InstructionContext, + owner: str, + base: int = 0, + flags: MemoryFlags = None, + ): self.name = name self.base = base self.context = context @@ -14,16 +28,26 @@ class BinaryDataMemorySection(MemorySection): def read(self, offset: T_RelativeAddress, size: int) -> bytearray: if offset + size > self.size: - raise MemoryAccessException("Out of bounds access in {}".format(self), offset, size, 'read') - return self.data[offset:offset + size] + raise MemoryAccessException( + "Out of bounds access in {}".format(self), offset, size, "read" + ) + return self.data[offset : offset + size] def write(self, offset: T_RelativeAddress, size: int, data: bytearray): if offset + size > self.size: - raise MemoryAccessException("Out of bounds access in {}".format(self), offset, size, 'write') + raise MemoryAccessException( + "Out of bounds access in {}".format(self), offset, size, "write" + ) if len(data[0:size]) != size: - raise MemoryAccessException("Invalid write parameter sizing", offset, size, 'write') - self.data[offset:offset + size] = data[0:size] + raise MemoryAccessException( + "Invalid write parameter sizing", offset, size, "write" + ) + self.data[offset : offset + size] = data[0:size] def read_ins(self, offset: T_RelativeAddress) -> Instruction: - raise MemoryAccessException("Tried reading instruction on non-executable section {}".format(self), - offset, 4, 'instruction fetch') + raise MemoryAccessException( + "Tried reading instruction on non-executable section {}".format(self), + offset, + 4, + "instruction fetch", + ) diff --git a/riscemu/types/cpu.py b/riscemu/types/cpu.py index 4672f0c..5a78056 100644 --- a/riscemu/types/cpu.py +++ b/riscemu/types/cpu.py @@ -14,7 +14,7 @@ class CPU(ABC): # housekeeping variables regs: Registers - mmu: 'MMU' + mmu: "MMU" pc: T_AbsoluteAddress cycle: int halted: bool @@ -24,12 +24,17 @@ class CPU(ABC): # instruction information instructions: Dict[str, Callable[[Instruction], None]] - instruction_sets: Set['InstructionSet'] + instruction_sets: Set["InstructionSet"] # configuration conf: RunConfig - def __init__(self, mmu: 'MMU', instruction_sets: List[Type['InstructionSet']], conf: RunConfig): + def __init__( + self, + mmu: "MMU", + instruction_sets: List[Type["InstructionSet"]], + conf: RunConfig, + ): self.mmu = mmu self.regs = Registers(conf.unlimited_registers) self.conf = conf @@ -71,7 +76,7 @@ class CPU(ABC): self.pc, self.cycle, self.halted, - " ".join(s.name for s in self.instruction_sets) + " ".join(s.name for s in self.instruction_sets), ) @abstractmethod @@ -84,12 +89,18 @@ class CPU(ABC): def launch(self, program: Program, verbose: bool = False): if program not in self.mmu.programs: - print(FMT_ERROR + '[CPU] Cannot launch program that\'s not loaded!' + FMT_NONE) + print( + FMT_ERROR + "[CPU] Cannot launch program that's not loaded!" + FMT_NONE + ) return if self.conf.verbosity > 0: - print(FMT_CPU + "[CPU] Started running from {}".format( - self.mmu.translate_address(program.entrypoint) - ) + FMT_NONE) + print( + FMT_CPU + + "[CPU] Started running from {}".format( + self.mmu.translate_address(program.entrypoint) + ) + + FMT_NONE + ) print(program) self.pc = program.entrypoint self.run(verbose) diff --git a/riscemu/types/exceptions.py b/riscemu/types/exceptions.py index 53af40a..71ee0a8 100644 --- a/riscemu/types/exceptions.py +++ b/riscemu/types/exceptions.py @@ -19,10 +19,13 @@ class RiscemuBaseException(BaseException): def print_stacktrace(self): import traceback + traceback.print_exception(type(self), self, self.__traceback__) + # Parsing exceptions: + class ParseException(RiscemuBaseException): def __init__(self, msg, data=None): super().__init__(msg, data) @@ -30,32 +33,47 @@ class ParseException(RiscemuBaseException): self.data = data def message(self): - return FMT_PARSE + "{}(\"{}\", data={})".format(self.__class__.__name__, self.msg, self.data) + FMT_NONE + return ( + FMT_PARSE + + '{}("{}", data={})'.format(self.__class__.__name__, self.msg, self.data) + + FMT_NONE + ) def ASSERT_EQ(a1, a2): if a1 != a2: - raise ParseException("ASSERTION_FAILED: Expected elements to be equal!", (a1, a2)) + raise ParseException( + "ASSERTION_FAILED: Expected elements to be equal!", (a1, a2) + ) def ASSERT_LEN(a1, size): if len(a1) != size: - raise ParseException("ASSERTION_FAILED: Expected {} to be of length {}".format(a1, size), (a1, size)) + raise ParseException( + "ASSERTION_FAILED: Expected {} to be of length {}".format(a1, size), + (a1, size), + ) def ASSERT_NOT_NULL(a1): if a1 is None: - raise ParseException("ASSERTION_FAILED: Expected {} to be non null".format(a1), (a1,)) + raise ParseException( + "ASSERTION_FAILED: Expected {} to be non null".format(a1), (a1,) + ) def ASSERT_NOT_IN(a1, a2): if a1 in a2: - raise ParseException("ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2)) + raise ParseException( + "ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2) + ) def ASSERT_IN(a1, a2): if a1 not in a2: - raise ParseException("ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2)) + raise ParseException( + "ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2) + ) class LinkerException(RiscemuBaseException): @@ -64,11 +82,16 @@ class LinkerException(RiscemuBaseException): self.data = data def message(self): - return FMT_PARSE + "{}(\"{}\", data={})".format(self.__class__.__name__, self.msg, self.data) + FMT_NONE + return ( + FMT_PARSE + + '{}("{}", data={})'.format(self.__class__.__name__, self.msg, self.data) + + FMT_NONE + ) # MMU Exceptions + class MemoryAccessException(RiscemuBaseException): def __init__(self, msg, addr, size, op): super(MemoryAccessException, self).__init__() @@ -78,13 +101,13 @@ class MemoryAccessException(RiscemuBaseException): self.op = op def message(self): - return FMT_MEM + "{}(During {} at 0x{:08x} of size {}: {})".format( - self.__class__.__name__, - self.op, - self.addr, - self.size, - self.msg - ) + FMT_NONE + return ( + FMT_MEM + + "{}(During {} at 0x{:08x} of size {}: {})".format( + self.__class__.__name__, self.op, self.addr, self.size, self.msg + ) + + FMT_NONE + ) class OutOfMemoryException(RiscemuBaseException): @@ -92,10 +115,13 @@ class OutOfMemoryException(RiscemuBaseException): self.action = action def message(self): - return FMT_MEM + '{}(Ran out of memory during {})'.format( - self.__class__.__name__, - self.action - ) + FMT_NONE + return ( + FMT_MEM + + "{}(Ran out of memory during {})".format( + self.__class__.__name__, self.action + ) + + FMT_NONE + ) class InvalidAllocationException(RiscemuBaseException): @@ -106,28 +132,29 @@ class InvalidAllocationException(RiscemuBaseException): self.flags = flags def message(self): - return FMT_MEM + '{}[{}](name={}, size={}, flags={})'.format( - self.__class__.__name__, - self.msg, - self.name, - self.size, - self.flags + return FMT_MEM + "{}[{}](name={}, size={}, flags={})".format( + self.__class__.__name__, self.msg, self.name, self.size, self.flags ) + # CPU Exceptions class UnimplementedInstruction(RiscemuBaseException): - def __init__(self, ins: 'Instruction', context = None): + def __init__(self, ins: "Instruction", context=None): self.ins = ins self.context = context def message(self): - return FMT_CPU + "{}({}{})".format( - self.__class__.__name__, - repr(self.ins), - ', context={}'.format(self.context) if self.context is not None else '' - ) + FMT_NONE + return ( + FMT_CPU + + "{}({}{})".format( + self.__class__.__name__, + repr(self.ins), + ", context={}".format(self.context) if self.context is not None else "", + ) + + FMT_NONE + ) class InvalidRegisterException(RiscemuBaseException): @@ -135,10 +162,11 @@ class InvalidRegisterException(RiscemuBaseException): self.reg = reg def message(self): - return FMT_CPU + "{}(Invalid register {})".format( - self.__class__.__name__, - self.reg - ) + FMT_NONE + return ( + FMT_CPU + + "{}(Invalid register {})".format(self.__class__.__name__, self.reg) + + FMT_NONE + ) class InvalidSyscallException(RiscemuBaseException): @@ -146,10 +174,11 @@ class InvalidSyscallException(RiscemuBaseException): self.scall = scall def message(self): - return FMT_SYSCALL + "{}(Invalid syscall: {})".format( - self.__class__.__name__, - self.scall - ) + FMT_NONE + return ( + FMT_SYSCALL + + "{}(Invalid syscall: {})".format(self.__class__.__name__, self.scall) + + FMT_NONE + ) def INS_NOT_IMPLEMENTED(ins): @@ -162,10 +191,7 @@ class NumberFormatException(RiscemuBaseException): self.msg = msg def message(self): - return "{}({})".format( - self.__class__.__name__, - self.msg - ) + return "{}({})".format(self.__class__.__name__, self.msg) # this exception is not printed and simply signals that an interactive debugging session is diff --git a/riscemu/types/flags.py b/riscemu/types/flags.py index 7c1a7e7..956d269 100644 --- a/riscemu/types/flags.py +++ b/riscemu/types/flags.py @@ -8,6 +8,5 @@ class MemoryFlags: def __repr__(self): return "r{}{}".format( - '-' if self.read_only else 'w', - 'x' if self.executable else '-' + "-" if self.read_only else "w", "x" if self.executable else "-" ) diff --git a/riscemu/types/instruction_context.py b/riscemu/types/instruction_context.py index 629b090..db50cec 100644 --- a/riscemu/types/instruction_context.py +++ b/riscemu/types/instruction_context.py @@ -33,21 +33,32 @@ class InstructionContext: self.base_address = 0 self.global_symbol_dict = dict() - def resolve_label(self, symbol: str, address_at: Optional[T_RelativeAddress] = None) -> Optional[T_AbsoluteAddress]: + def resolve_label( + self, symbol: str, address_at: Optional[T_RelativeAddress] = None + ) -> Optional[T_AbsoluteAddress]: if NUMBER_SYMBOL_PATTERN.match(symbol): if address_at is None: - raise ParseException("Cannot resolve relative symbol {} without an address!".format(symbol)) + raise ParseException( + "Cannot resolve relative symbol {} without an address!".format( + symbol + ) + ) direction = symbol[-1] values = self.numbered_labels.get(symbol[:-1], []) - if direction == 'b': - return max((addr + self.base_address for addr in values if addr < address_at), default=None) + if direction == "b": + return max( + (addr + self.base_address for addr in values if addr < address_at), + default=None, + ) else: - return min((addr + self.base_address for addr in values if addr > address_at), default=None) + return min( + (addr + self.base_address for addr in values if addr > address_at), + default=None, + ) else: # if it's not a local symbol, try the globals if symbol not in self.labels: return self.global_symbol_dict.get(symbol, None) # otherwise return the local symbol return self.labels.get(symbol, None) - diff --git a/riscemu/types/instruction_memory_section.py b/riscemu/types/instruction_memory_section.py index 1a2e8e0..72a99f9 100644 --- a/riscemu/types/instruction_memory_section.py +++ b/riscemu/types/instruction_memory_section.py @@ -1,11 +1,24 @@ from typing import List -from . import MemorySection, Instruction, InstructionContext, MemoryFlags, T_RelativeAddress +from . import ( + MemorySection, + Instruction, + InstructionContext, + MemoryFlags, + T_RelativeAddress, +) from .exceptions import MemoryAccessException class InstructionMemorySection(MemorySection): - def __init__(self, instructions: List[Instruction], name: str, context: InstructionContext, owner: str, base: int = 0): + def __init__( + self, + instructions: List[Instruction], + name: str, + context: InstructionContext, + owner: str, + base: int = 0, + ): self.name = name self.base = base self.context = context @@ -15,13 +28,27 @@ class InstructionMemorySection(MemorySection): self.owner = owner def read(self, offset: T_RelativeAddress, size: int) -> bytearray: - raise MemoryAccessException("Cannot read raw bytes from instruction section", self.base + offset, size, 'read') + raise MemoryAccessException( + "Cannot read raw bytes from instruction section", + self.base + offset, + size, + "read", + ) def write(self, offset: T_RelativeAddress, size: int, data: bytearray): - raise MemoryAccessException("Cannot write raw bytes to instruction section", self.base + offset, size, 'write') + raise MemoryAccessException( + "Cannot write raw bytes to instruction section", + self.base + offset, + size, + "write", + ) def read_ins(self, offset: T_RelativeAddress) -> Instruction: if offset % 4 != 0: - raise MemoryAccessException("Unaligned instruction fetch!", self.base + offset, 4, 'instruction fetch') + raise MemoryAccessException( + "Unaligned instruction fetch!", + self.base + offset, + 4, + "instruction fetch", + ) return self.instructions[offset // 4] - diff --git a/riscemu/types/int32.py b/riscemu/types/int32.py index 1d61a85..258d720 100644 --- a/riscemu/types/int32.py +++ b/riscemu/types/int32.py @@ -11,13 +11,18 @@ class Int32: You can use it just like you would any other integer, just be careful when passing it to functions which actually expect an integer and not a Int32. """ + _type = c_int32 - __slots__ = ('_val',) + __slots__ = ("_val",) - def __init__(self, val: Union[int, c_int32, c_uint32, 'Int32', bytes, bytearray] = 0): + def __init__( + self, val: Union[int, c_int32, c_uint32, "Int32", bytes, bytearray] = 0 + ): if isinstance(val, (bytes, bytearray)): signed = len(val) == 4 and self._type == c_int32 - self._val = self.__class__._type(int.from_bytes(val, 'little', signed=signed)) + self._val = self.__class__._type( + int.from_bytes(val, "little", signed=signed) + ) elif isinstance(val, self.__class__._type): self._val = val elif isinstance(val, (c_uint32, c_int32, Int32)): @@ -26,21 +31,23 @@ class Int32: self._val = self.__class__._type(val) else: raise RuntimeError( - "Unknonw {} input type: {} ({})".format(self.__class__.__name__, type(val), val) + "Unknonw {} input type: {} ({})".format( + self.__class__.__name__, type(val), val + ) ) - def __add__(self, other: Union['Int32', int]): + 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]): + 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]): + def __mul__(self, other: Union["Int32", int]): if isinstance(other, Int32): other = other.value return self.__class__(self._val.value * other) @@ -53,37 +60,37 @@ class Int32: other = other.value return self.__class__(self.value // other) - def __mod__(self, other: Union['Int32', int]): + 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]): + 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]): + 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]): + 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]): + 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]): + 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]): + def __eq__(self, other: Union["Int32", int]): if isinstance(other, Int32): other = other.value return self.value == other @@ -98,7 +105,7 @@ class Int32: return self.to_bytes(4) def __repr__(self): - return '{}({})'.format(self.__class__.__name__, self.value) + return "{}({})".format(self.__class__.__name__, self.value) def __str__(self): return str(self.value) @@ -174,7 +181,7 @@ class Int32: """ return self._val.value - def unsigned(self) -> 'UInt32': + def unsigned(self) -> "UInt32": """ Convert to an unsigned representation. See :class:Uint32 :return: @@ -188,9 +195,9 @@ class Int32: :param bytes: The length of the bytearray :return: A little-endian representation of the contained integer """ - return bytearray(self.unsigned_value.to_bytes(4, 'little'))[0:bytes] + return bytearray(self.unsigned_value.to_bytes(4, "little"))[0:bytes] - def signed(self) -> 'Int32': + def signed(self) -> "Int32": """ Convert to a signed representation. See :class:Int32 :return: @@ -207,7 +214,7 @@ class Int32: """ return c_uint32(self.value).value - def shift_right_logical(self, ammount: Union['Int32', int]) -> 'Int32': + def shift_right_logical(self, ammount: Union["Int32", int]) -> "Int32": """ This function implements logical right shifts, meaning that the sign bit is shifted as well. @@ -237,12 +244,12 @@ class Int32: :return: An instance of Int32, holding the sign-extended value """ if isinstance(data, (bytes, bytearray)): - data = int.from_bytes(data, 'little') + data = int.from_bytes(data, "little") sign = data >> (bits - 1) if sign > 1: print("overflow in Int32.sext!") if sign: - data = (data & (2 ** (bits - 1) - 1)) - 2**(bits-1) + data = (data & (2 ** (bits - 1) - 1)) - 2 ** (bits - 1) return cls(data) @@ -250,9 +257,10 @@ class UInt32(Int32): """ An unsigned version of :class:Int32. """ + _type = c_uint32 - def unsigned(self) -> 'UInt32': + def unsigned(self) -> "UInt32": """ Return a new instance representing the same bytes, but signed :return: @@ -263,7 +271,7 @@ class UInt32(Int32): def unsigned_value(self) -> int: return self._val.value - def shift_right_logical(self, ammount: Union['Int32', int]) -> 'UInt32': + def shift_right_logical(self, ammount: Union["Int32", int]) -> "UInt32": """ see :meth:`Int32.shift_right_logical ` diff --git a/riscemu/types/memory_section.py b/riscemu/types/memory_section.py index ab6cbea..3075859 100644 --- a/riscemu/types/memory_section.py +++ b/riscemu/types/memory_section.py @@ -4,7 +4,14 @@ from typing import Optional from ..colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE, FMT_ERROR from ..helpers import format_bytes -from . import MemoryFlags, T_AbsoluteAddress, InstructionContext, T_RelativeAddress, Instruction, Int32 +from . import ( + MemoryFlags, + T_AbsoluteAddress, + InstructionContext, + T_RelativeAddress, + Instruction, + Int32, +) @dataclass @@ -32,8 +39,16 @@ class MemorySection(ABC): def read_ins(self, offset: T_RelativeAddress) -> Instruction: pass - def dump(self, start: T_RelativeAddress, end: Optional[T_RelativeAddress] = None, fmt: str = None, - bytes_per_row: int = None, rows: int = 10, group: int = None, highlight: int = None): + def dump( + self, + start: T_RelativeAddress, + end: Optional[T_RelativeAddress] = None, + fmt: str = None, + bytes_per_row: int = None, + rows: int = 10, + group: int = None, + highlight: int = None, + ): """ Dump the section. If no end is given, the rows around start are printed and start is highlighted. @@ -54,11 +69,11 @@ class MemorySection(ABC): if fmt is None: if self.flags.executable and self.flags.read_only: bytes_per_row = 4 - fmt = 'asm' + fmt = "asm" else: - fmt = 'hex' + fmt = "hex" - if fmt == 'char': + if fmt == "char": if bytes_per_row is None: bytes_per_row = 4 if group is None: @@ -70,47 +85,72 @@ class MemorySection(ABC): if bytes_per_row is None: bytes_per_row = 4 - if fmt not in ('asm', 'hex', 'int', 'char'): - print(FMT_ERROR + '[MemorySection] Unknown format {}, known formats are {}'.format( - fmt, ", ".join(('asm', 'hex', 'int', 'char')) - ) + FMT_NONE) + if fmt not in ("asm", "hex", "int", "char"): + print( + FMT_ERROR + + "[MemorySection] Unknown format {}, known formats are {}".format( + fmt, ", ".join(("asm", "hex", "int", "char")) + ) + + FMT_NONE + ) if end is None: end = min(start + (bytes_per_row * (rows // 2)), self.size) highlight = start start = max(0, start - (bytes_per_row * (rows // 2))) - if fmt == 'asm': - print(FMT_MEM + "{}, viewing {} instructions:".format( - self, (end - start) // 4 - ) + FMT_NONE) + if fmt == "asm": + print( + FMT_MEM + + "{}, viewing {} instructions:".format(self, (end - start) // 4) + + FMT_NONE + ) for addr in range(start, end, 4): if addr == highlight: - print(FMT_UNDERLINE + FMT_ORANGE, end='') - print("0x{:04x}: {}{}".format( - self.base + addr, self.read_ins(addr), FMT_NONE - )) + print(FMT_UNDERLINE + FMT_ORANGE, end="") + print( + "0x{:04x}: {}{}".format( + self.base + addr, self.read_ins(addr), FMT_NONE + ) + ) else: - print(FMT_MEM + "{}, viewing {} bytes:".format( - self, (end - start) - ) + FMT_NONE) + print( + FMT_MEM + "{}, viewing {} bytes:".format(self, (end - start)) + FMT_NONE + ) - aligned_end = end - (end % bytes_per_row) if end % bytes_per_row != 0 else end + aligned_end = ( + end - (end % bytes_per_row) if end % bytes_per_row != 0 else end + ) for addr in range(start, aligned_end, bytes_per_row): hi_ind = (highlight - addr) // group if highlight is not None else -1 - print("0x{:04x}: {}{}".format( - self.base + addr, format_bytes(self.read(addr, bytes_per_row), fmt, group, hi_ind), FMT_NONE - )) + print( + "0x{:04x}: {}{}".format( + self.base + addr, + format_bytes( + self.read(addr, bytes_per_row), fmt, group, hi_ind + ), + FMT_NONE, + ) + ) if aligned_end != end: - hi_ind = (highlight - aligned_end) // group if highlight is not None else -1 - print("0x{:04x}: {}{}".format( - self.base + aligned_end, format_bytes( - self.read(aligned_end, end % bytes_per_row), fmt, group, hi_ind - ), FMT_NONE - )) + hi_ind = ( + (highlight - aligned_end) // group if highlight is not None else -1 + ) + print( + "0x{:04x}: {}{}".format( + self.base + aligned_end, + format_bytes( + self.read(aligned_end, end % bytes_per_row), + fmt, + group, + hi_ind, + ), + FMT_NONE, + ) + ) def dump_all(self, *args, **kwargs): self.dump(0, self.size, *args, **kwargs) @@ -122,5 +162,5 @@ class MemorySection(ABC): self.base, self.size, self.flags, - self.owner + self.owner, ) diff --git a/riscemu/types/program.py b/riscemu/types/program.py index 24533f7..38e816b 100644 --- a/riscemu/types/program.py +++ b/riscemu/types/program.py @@ -14,6 +14,7 @@ class Program: the offset in the program, and everything will be taken care of for you. """ + name: str context: InstructionContext global_labels: Set[str] @@ -44,9 +45,13 @@ class Program: 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( + FMT_RED + + FMT_BOLD + + "WARNING: memory section {} in {} is placed before program base (0x{:x})".format( sec, self.name, self.base - ) + FMT_NONE) + ) + + FMT_NONE + ) self.sections.append(sec) # keep section list ordered @@ -54,18 +59,21 @@ class Program: def __repr__(self): return "{}(name={},sections={},base={})".format( - self.__class__.__name__, self.name, self.global_labels, - [s.name for s in self.sections], self.base + self.__class__.__name__, + self.name, + self.global_labels, + [s.name for s in self.sections], + self.base, ) @property def entrypoint(self): - if '_start' in self.context.labels: - return self.context.labels.get('_start') - if 'main' in self.context.labels: - return self.context.labels.get('main') + if "_start" in self.context.labels: + return self.context.labels.get("_start") + if "main" in self.context.labels: + return self.context.labels.get("main") for sec in self.sections: - if get_section_base_name(sec.name) == '.text' and sec.flags.executable: + if get_section_base_name(sec.name) == ".text" and sec.flags.executable: return sec.base def loaded_trigger(self, at_addr: T_AbsoluteAddress): @@ -81,12 +89,17 @@ class Program: """ if self.is_loaded: if at_addr != self.base: - raise RuntimeError("Program loaded twice at different addresses! This will probably break things!") + raise RuntimeError( + "Program loaded twice at different addresses! This will probably break things!" + ) return if self.base is not None and self.base != at_addr: - print(FMT_MEM + 'WARNING: Program loaded at different address then expected! (loaded at {}, ' - 'but expects to be loaded at {})'.format(at_addr, self.base) + FMT_NONE) + print( + FMT_MEM + + "WARNING: Program loaded at different address then expected! (loaded at {}, " + "but expects to be loaded at {})".format(at_addr, self.base) + FMT_NONE + ) # check if we are relocating if self.base != at_addr: diff --git a/riscemu/types/program_loader.py b/riscemu/types/program_loader.py index 65db90f..f069dfb 100644 --- a/riscemu/types/program_loader.py +++ b/riscemu/types/program_loader.py @@ -39,7 +39,7 @@ class ProgramLoader(ABC): pass @classmethod - def instantiate(cls, source_path: str, options: T_ParserOpts) -> 'ProgramLoader': + def instantiate(cls, source_path: str, options: T_ParserOpts) -> "ProgramLoader": """ Instantiate a loader for the given source file with the required arguments diff --git a/riscemu/types/simple_instruction.py b/riscemu/types/simple_instruction.py index 59d7b6c..39aba1c 100644 --- a/riscemu/types/simple_instruction.py +++ b/riscemu/types/simple_instruction.py @@ -5,8 +5,13 @@ from ..helpers import parse_numeric_argument class SimpleInstruction(Instruction): - def __init__(self, name: str, args: Union[Tuple[()], Tuple[str], Tuple[str, str], Tuple[str, str, str]], - context: InstructionContext, addr: T_RelativeAddress): + def __init__( + self, + name: str, + args: Union[Tuple[()], Tuple[str], Tuple[str, str], Tuple[str, str, str]], + context: InstructionContext, + addr: T_RelativeAddress, + ): self.context = context self.name = name self.args = args @@ -23,4 +28,3 @@ class SimpleInstruction(Instruction): def get_reg(self, num: int) -> str: return self.args[num] - diff --git a/test/__init__.py b/test/__init__.py index 1f2c0dd..05d70ec 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,3 +1,3 @@ from .test_tokenizer import * from .test_helpers import * -from .test_integers import * \ No newline at end of file +from .test_integers import * diff --git a/test/test_helpers.py b/test/test_helpers.py index 3b27f2e..3e13161 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -1,6 +1,6 @@ - from riscemu.helpers import * + def test_align_address(): assert align_addr(3, 1) == 3 assert align_addr(3, 2) == 4 @@ -10,6 +10,6 @@ def test_align_address(): def test_parse_numeric(): - assert parse_numeric_argument('13') == 13 - assert parse_numeric_argument('0x100') == 256 - assert parse_numeric_argument('-13') == -13 + assert parse_numeric_argument("13") == 13 + assert parse_numeric_argument("0x100") == 256 + assert parse_numeric_argument("-13") == -13 diff --git a/test/test_isa.py b/test/test_isa.py index 3ec1ae0..2fdd455 100644 --- a/test/test_isa.py +++ b/test/test_isa.py @@ -38,36 +38,44 @@ def get_arg_from_ins(ins: Instruction, num: int, cpu: CPU): assert_ops = { - '==': assert_equals, - '!=': _not(assert_equals), - 'in': assert_in, - 'not_in': _not(assert_in), + "==": assert_equals, + "!=": _not(assert_equals), + "in": assert_in, + "not_in": _not(assert_in), } class Z_test(InstructionSet): - def __init__(self, cpu: 'CPU'): - print('[Test] loading testing ISA, this is only meant for running testcases and is not part of the RISC-V ISA!') + def __init__(self, cpu: "CPU"): + print( + "[Test] loading testing ISA, this is only meant for running testcases and is not part of the RISC-V ISA!" + ) self.failed = False super().__init__(cpu) def instruction_assert(self, ins: Instruction): if len(ins.args) < 3: - print(FMT_ERROR + '[Test] Unknown assert statement: {}'.format(ins) + FMT_NONE) + print( + FMT_ERROR + "[Test] Unknown assert statement: {}".format(ins) + FMT_NONE + ) return op = ins.args[1] if op not in assert_ops: - print(FMT_ERROR + '[Test] Unknown operation statement: {} in {}'.format(op, ins) + FMT_NONE) + print( + FMT_ERROR + + "[Test] Unknown operation statement: {} in {}".format(op, ins) + + FMT_NONE + ) return if assert_ops[op](ins, self.cpu): - print(FMT_SUCCESS + '[TestCase] 🟢 passed assertion {}'.format(ins)) + print(FMT_SUCCESS + "[TestCase] 🟢 passed assertion {}".format(ins)) else: - print(FMT_ERROR + '[TestCase] 🔴 failed assertion {}'.format(ins)) + print(FMT_ERROR + "[TestCase] 🔴 failed assertion {}".format(ins)) self.cpu.halted = True self.failed = True def instruction_fail(self, ins: Instruction): - print(FMT_ERROR + '[TestCase] 🔴 reached fail instruction! {}'.format(ins)) - self.cpu.halted = True - self.failed = True + print(FMT_ERROR + "[TestCase] 🔴 reached fail instruction! {}".format(ins)) + self.cpu.halted = True + self.failed = True diff --git a/test/test_tokenizer.py b/test/test_tokenizer.py index 9eed365..4f2fbd9 100644 --- a/test/test_tokenizer.py +++ b/test/test_tokenizer.py @@ -1,7 +1,14 @@ from unittest import TestCase -from riscemu.tokenizer import tokenize, print_tokens, Token, TokenType, NEWLINE, COMMA, \ - split_whitespace_respecting_quotes +from riscemu.tokenizer import ( + tokenize, + print_tokens, + Token, + TokenType, + NEWLINE, + COMMA, + split_whitespace_respecting_quotes, +) def ins(name: str) -> Token: @@ -21,41 +28,49 @@ def lbl(name: str) -> Token: class TestTokenizer(TestCase): - def test_instructions(self): - program = [ - 'li a0, 144', - 'divi a0, a0, 12', - 'xori a1, a0, 12' - ] + program = ["li a0, 144", "divi a0, a0, 12", "xori a1, a0, 12"] tokens = [ - ins('li'), arg('a0'), COMMA, arg('144'), NEWLINE, - ins('divi'), arg('a0'), COMMA, arg('a0'), COMMA, arg('12'), NEWLINE, - ins('xori'), arg('a1'), COMMA, arg('a0'), COMMA, arg('12'), NEWLINE, + ins("li"), + arg("a0"), + COMMA, + arg("144"), + NEWLINE, + ins("divi"), + arg("a0"), + COMMA, + arg("a0"), + COMMA, + arg("12"), + NEWLINE, + ins("xori"), + arg("a1"), + COMMA, + arg("a0"), + COMMA, + arg("12"), + NEWLINE, ] self.assertEqual(list(tokenize(program)), tokens) def test_comments(self): - parsed_res = [ - ins('li'), arg('a0'), COMMA, arg('144'), NEWLINE - ] - for c in ('#', '//', ';'): - lines = [ - c + ' this is a comment', - 'li a0, 144' - ] + parsed_res = [ins("li"), arg("a0"), COMMA, arg("144"), NEWLINE] + for c in ("#", "//", ";"): + lines = [c + " this is a comment", "li a0, 144"] self.assertEqual(list(tokenize(lines)), parsed_res) def test_pseudo_ins(self): parsed_res = [ - Token(TokenType.PSEUDO_OP, '.section'), Token(TokenType.ARGUMENT, '.text'), NEWLINE, - Token(TokenType.PSEUDO_OP, '.type'), Token(TokenType.ARGUMENT, 'init'), COMMA, - Token(TokenType.ARGUMENT, '@function'), NEWLINE - ] - input_program = [ - '.section .text', - '.type init, @function' + Token(TokenType.PSEUDO_OP, ".section"), + Token(TokenType.ARGUMENT, ".text"), + NEWLINE, + Token(TokenType.PSEUDO_OP, ".type"), + Token(TokenType.ARGUMENT, "init"), + COMMA, + Token(TokenType.ARGUMENT, "@function"), + NEWLINE, ] + input_program = [".section .text", ".type init, @function"] self.assertEqual(list(tokenize(input_program)), parsed_res) def test_full_program(self): @@ -71,24 +86,40 @@ section: sub s0, s0, s0 """ tokens = [ - op('.section'), arg('.text'), NEWLINE, - ins('addi'), arg('sp'), COMMA, arg('sp'), COMMA, arg('-32'), NEWLINE, - ins('sw'), arg('s0'), COMMA, arg('ra'), arg('0'), NEWLINE, - lbl('section:'), NEWLINE, - ins('sub'), arg('s0'), COMMA, arg('s0'), COMMA, arg('s0'), NEWLINE + op(".section"), + arg(".text"), + NEWLINE, + ins("addi"), + arg("sp"), + COMMA, + arg("sp"), + COMMA, + arg("-32"), + NEWLINE, + ins("sw"), + arg("s0"), + COMMA, + arg("ra"), + arg("0"), + NEWLINE, + lbl("section:"), + NEWLINE, + ins("sub"), + arg("s0"), + COMMA, + arg("s0"), + COMMA, + arg("s0"), + NEWLINE, ] self.assertEqual(list(tokenize(program.splitlines())), tokens) def test_split_whitespace_respecting_quotes_single(self): - self.assertEqual( - list(split_whitespace_respecting_quotes("test")), ["test"] - ) + self.assertEqual(list(split_whitespace_respecting_quotes("test")), ["test"]) def test_split_whitespace_respecting_quotes_empty(self): - self.assertEqual( - list(split_whitespace_respecting_quotes("")), [] - ) + self.assertEqual(list(split_whitespace_respecting_quotes("")), []) def test_split_whitespace_respecting_quotes_two_parts(self): self.assertEqual( @@ -107,20 +138,24 @@ section: def test_split_whitespace_respecting_quotes_quoted_then_normal(self): self.assertEqual( - list(split_whitespace_respecting_quotes('"test 123" abc')), ["test 123", "abc"] + list(split_whitespace_respecting_quotes('"test 123" abc')), + ["test 123", "abc"], ) def test_split_whitespace_respecting_quotes_quoted_sorrounded(self): self.assertEqual( - list(split_whitespace_respecting_quotes('hello "test 123" abc')), ["hello", "test 123", "abc"] + list(split_whitespace_respecting_quotes('hello "test 123" abc')), + ["hello", "test 123", "abc"], ) def test_split_whitespace_respecting_quotes_weird_spaces(self): self.assertEqual( - list(split_whitespace_respecting_quotes('hello "test 123"\tabc')), ["hello", "test 123", "abc"] + list(split_whitespace_respecting_quotes('hello "test 123"\tabc')), + ["hello", "test 123", "abc"], ) def test_split_whitespace_respecting_quotes_quotes_no_spaces(self): self.assertEqual( - list(split_whitespace_respecting_quotes('hello"test 123"abc')), ["hello", "test 123", "abc"] + list(split_whitespace_respecting_quotes('hello"test 123"abc')), + ["hello", "test 123", "abc"], )