user mode emulator finally working again

assembly-parser-rework
Anton Lydike 3 years ago
parent 5538034f8b
commit b396e0c5eb

@ -1,142 +1,106 @@
"""
RiscEmu (c) 2021 Anton Lydike
RiscEmu (c) 2021-2022 Anton Lydike
SPDX-License-Identifier: MIT
This file contains the CPU logic (not the individual instruction sets). See instructions/InstructionSet.py for more info
This file contains the CPU logic (not the individual instruction sets). See instructions/instruction_set.py for more info
on them.
"""
import sys
from typing import Tuple, List, Dict, Callable, Type
import typing
from typing import List, Type
from .types import MemoryFlags
from .syscall import SyscallInterface, get_syscall_symbols
from .exceptions import RiscemuBaseException, LaunchDebuggerException
import riscemu
from .MMU import MMU
from .config import RunConfig
from .registers import Registers
from .base import BinaryDataMemorySection
from .colors import FMT_CPU, FMT_NONE
from .debug import launch_debug_session
from .colors import FMT_CPU, FMT_NONE, FMT_ERROR
import riscemu
import typing
from .exceptions import RiscemuBaseException, LaunchDebuggerException
from .syscall import SyscallInterface, get_syscall_symbols
from .types import CPU
if typing.TYPE_CHECKING:
from . import types, LoadedExecutable, LoadedInstruction
from .instructions.InstructionSet import InstructionSet
from .instructions.instruction_set import InstructionSet
class CPU:
class UserModeCPU(CPU):
"""
This class represents a single CPU. It holds references to it's mmu, registers and syscall interrupt handler.
It is initialized with a configuration and a list of instruction sets.
"""
INS_XLEN = 4
def __init__(self, conf: RunConfig, instruction_sets: List[Type['riscemu.InstructionSet']]):
def __init__(self, instruction_sets: List[Type['riscemu.InstructionSet']]):
"""
Creates a CPU instance.
:param conf: An instance of the current RunConfiguration
:param instruction_sets: A list of instruction set classes. These must inherit from the InstructionSet class
"""
# setup CPU states
self.pc = 0
self.cycle = 0
self.exit: bool = False
self.exit_code: int = 0
self.conf = conf
self.active_debug = False # if a debugging session is currently runnign
self.stack: typing.Optional['riscemu.LoadedMemorySection'] = None
# setup MMU, registers and syscall handlers
self.mmu = MMU(conf)
self.regs = Registers(conf)
self.syscall_int = SyscallInterface()
# load all instruction sets
self.instruction_sets: List[riscemu.InstructionSet] = list()
self.instructions: Dict[str, Callable[[LoadedInstruction], None]] = dict()
for set_class in instruction_sets:
ins_set = set_class(self)
self.instructions.update(ins_set.load())
self.instruction_sets.append(ins_set)
super().__init__(MMU(), instruction_sets)
# provide global syscall symbols if option is set
if conf.include_scall_symbols:
self.mmu.global_symbols.update(get_syscall_symbols())
self.exit_code = 0
def continue_from_debugger(self, verbose=True):
"""
called from the debugger to continue running
# setup syscall interface
self.syscall_int = SyscallInterface()
:param verbose: If True, will print each executed instruction to STDOUT
"""
self._run(verbose)
# add global syscall symbols, but don't overwrite any user-defined symbols
syscall_symbols = get_syscall_symbols()
syscall_symbols.update(self.mmu.global_symbols)
self.mmu.global_symbols.update(syscall_symbols)
def step(self):
def step(self, verbose=False):
"""
Execute a single instruction, then return.
"""
if self.exit:
if self.halted:
print(FMT_CPU + "[CPU] Program exited with code {}".format(self.exit_code) + FMT_NONE)
else:
try:
self.cycle += 1
ins = self.mmu.read_ins(self.pc)
print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins))
self.pc += self.INS_XLEN
self.run_instruction(ins)
except LaunchDebuggerException:
print(FMT_CPU + "[CPU] Returning to debugger!" + FMT_NONE)
except RiscemuBaseException as ex:
self.pc -= self.INS_XLEN
print(ex.message())
return
launch_debugger = False
def _run(self, verbose=False):
if self.pc <= 0:
return False
ins = None
try:
while not self.exit:
self.cycle += 1
ins = self.mmu.read_ins(self.pc)
if verbose:
print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins))
self.pc += self.INS_XLEN
self.run_instruction(ins)
self.cycle += 1
ins = self.mmu.read_ins(self.pc)
if verbose:
print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins))
self.pc += self.INS_XLEN
self.run_instruction(ins)
except RiscemuBaseException as ex:
if not isinstance(ex, LaunchDebuggerException):
print(FMT_ERROR + "[CPU] excpetion caught at 0x{:08X}: {}:".format(self.pc - 1, ins) + FMT_NONE)
if isinstance(ex, LaunchDebuggerException):
# if the debugger is active, raise the exception to
if self.debugger_active:
raise ex
print(FMT_CPU + '[CPU] Debugger launch requested!' + FMT_NONE)
launch_debugger = True
else:
print(ex.message())
self.pc -= self.INS_XLEN
if self.active_debug:
print(FMT_CPU + "[CPU] Returning to debugger!" + FMT_NONE)
return
if self.conf.debug_on_exception:
launch_debug_session(self, self.mmu, self.regs, "Exception encountered, launching debug:")
if self.exit:
print()
print(FMT_CPU + "Program exited with code {}".format(self.exit_code) + FMT_NONE)
sys.exit(self.exit_code)
else:
print()
print(FMT_CPU + "Program stopped without exiting - perhaps you stopped the debugger?" + FMT_NONE)
def __repr__(self):
ex.print_stacktrace()
print(FMT_CPU + '[CPU] Halting due to exception!' + FMT_NONE)
self.halted = True
if launch_debugger:
launch_debug_session(self)
def run(self, verbose=False):
while not self.halted:
self.step(verbose)
def setup_stack(self, stack_size=1024 * 4) -> bool:
"""
Returns a representation of the CPU and some of its state.
Create program stack and populate stack pointer
:param stack_size: the size of the required stack, defaults to 4Kib
:return:
"""
return "{}(pc=0x{:08X}, cycle={}, exit={}, instructions={})".format(
self.__class__.__name__,
self.pc,
self.cycle,
self.exit,
" ".join(s.name for s in self.instruction_sets)
stack_sec = BinaryDataMemorySection(
bytearray(stack_size),
'.stack',
None, # FIXME: why does a binary data memory section require a context?
'',
0
)
if not self.mmu.load_section(stack_sec, fixed_position=False):
return False
self.regs.set('sp', stack_sec.base + stack_sec.size)

@ -49,9 +49,11 @@ class MMU:
"""
Create a new MMU
"""
self.programs = list()
self.sections = list()
self.global_symbols = dict()
def get_sec_containing(self, addr: T_AbsoluteAddress) -> Optional[MemorySection]:
"""
Returns the section that contains the address addr
@ -79,8 +81,8 @@ class MMU:
"""
sec = self.get_sec_containing(addr)
if sec is None:
print(FMT_MEM + "[MMU] Trying to read instruction form invalid region! "
"Have you forgotten an exit syscall or ret statement?" + FMT_NONE)
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)
@ -181,8 +183,7 @@ class MMU:
at_addr = program.base
else:
first_guaranteed_free_address = self.sections[-1].base + self.sections[-1].size
at_addr = align_addr(first_guaranteed_free_address, align_to)
at_addr = align_addr(self.get_guaranteed_free_address(), align_to)
# trigger the load event to set all addresses in the binary
program.loaded_trigger(at_addr)
@ -200,10 +201,35 @@ class MMU:
# FIXME: this is pretty unclean and should probably be solved in a better way in the future
program.context.global_symbol_dict = self.global_symbols
def load_section(self, sec: MemorySection, fixed_position: bool = False) -> bool:
if fixed_position:
if self.has_continous_free_region(sec.base, sec.base + sec.size):
self.sections.append(sec)
self._update_state()
else:
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)
sec.base = at_addr
self.sections.append(sec)
self._update_state()
return True
def _update_state(self):
"""
Called whenever a section or program is added to keep the list of programs and sections consistent
:return:
"""
self.programs.sort(key=lambda bin: bin.base)
self.sections.sort(key=lambda sec: sec.base)
def get_guaranteed_free_address(self) -> T_AbsoluteAddress:
if len(self.sections) == 0:
return 0x100
else:
return self.sections[-1].base + self.sections[-1].size
def __repr__(self):
return "MMU(\n\t{}\n)".format(
"\n\t".join(repr(x) for x in self.programs)

@ -5,17 +5,18 @@ SPDX-License-Identifier: MIT
This file holds the logic for starting the emulator from the CLI
"""
from riscemu.CPU import UserModeCPU
if __name__ == '__main__':
from . import *
from .config import RunConfig
from .helpers import *
from .instructions import InstructionSetDict
from riscemu.parser import AssemblyFileLoader
import argparse
import sys
all_ins_names = list(InstructionSetDict.keys())
class OptionStringAction(argparse.Action):
def __init__(self, option_strings, dest, keys=None, omit_empty=False, **kwargs):
if keys is None:
@ -93,17 +94,21 @@ if __name__ == '__main__':
]
try:
cpu = CPU(cfg, ins_to_load)
loaded_exe = None
cpu = UserModeCPU(ins_to_load)
opts = AssemblyFileLoader.get_options(sys.argv)
for file in args.files:
tk = cpu.get_tokenizer(RiscVInput.from_file(file))
tk.tokenize()
loaded_exe = cpu.load(ExecutableParser(tk).parse())
loader = AssemblyFileLoader.instantiate(file, opts)
cpu.load_program(loader.parse())
# run the last loaded executable
cpu.run_loaded(loaded_exe)
cpu.setup_stack(cfg.stack_size)
# launch the last loaded program
cpu.launch(cpu.mmu.programs[-1])
except RiscemuBaseException as e:
print("Error while parsing: {}".format(e.message()))
import traceback
print("Error: {}".format(e.message()))
e.print_stacktrace()
traceback.print_exception(type(e), e, e.__traceback__)
sys.exit(1)

@ -1,11 +1,10 @@
"""
RiscEmu (c) 2021 Anton Lydike
RiscEmu (c) 2021-2022 Anton Lydike
SPDX-License-Identifier: MIT
"""
from dataclasses import dataclass
from typing import Optional
@dataclass(frozen=True, init=True)

@ -4,35 +4,33 @@ RiscEmu (c) 2021 Anton Lydike
SPDX-License-Identifier: MIT
"""
import typing
from .registers import Registers
from .colors import FMT_DEBUG, FMT_NONE
from .types import Instruction
from .base import SimpleInstruction
from .helpers import *
if typing.TYPE_CHECKING:
from . import *
from riscemu import CPU, Registers
def launch_debug_session(cpu: 'CPU', mmu: 'MMU', reg: 'Registers', prompt=""):
if not cpu.conf.debug_instruction or cpu.active_debug:
def launch_debug_session(cpu: 'CPU', prompt=""):
if cpu.debugger_active:
return
import code
import readline
import rlcompleter
cpu.active_debug = True
# set the active debug flag
cpu.debugger_active = True
# setup some aliases:
registers = reg
regs = reg
memory = mmu
mem = mmu
syscall_interface = cpu.syscall_int
registers = cpu.regs
regs = cpu.regs
memory = cpu.mmu
mem = cpu.mmu
mmu = cpu.mmu
# setup helper functions:
def dump(what, *args, **kwargs):
if isinstance(what, Registers):
if what == regs:
regs.dump(*args, **kwargs)
else:
mmu.dump(what, *args, **kwargs)
@ -50,20 +48,39 @@ def launch_debug_session(cpu: 'CPU', mmu: 'MMU', reg: 'Registers', prompt=""):
return
bin = mmu.get_bin_containing(cpu.pc)
ins = Instruction(name, list(args), bin)
print(FMT_DEBUG + "Running instruction " + ins + FMT_NONE)
ins = SimpleInstruction(
name,
tuple(args),
bin.context,
cpu.pc)
print(FMT_DEBUG + "Running instruction {}".format(ins) + FMT_NONE)
cpu.run_instruction(ins)
def cont(verbose=False):
cpu.continue_from_debugger(verbose)
try:
cpu.run(verbose)
except LaunchDebuggerException:
print(FMT_DEBUG + 'Returning to debugger...')
return
def step():
cpu.step()
try:
cpu.step()
except LaunchDebuggerException:
return
# collect all variables
sess_vars = globals()
sess_vars.update(locals())
# add tab completion
readline.set_completer(rlcompleter.Completer(sess_vars).complete)
readline.parse_and_bind("tab: complete")
code.InteractiveConsole(sess_vars).interact(banner=FMT_DEBUG + prompt + FMT_NONE, exitmsg="Exiting debugger")
cpu.active_debug = False
relaunch_debugger = False
try:
code.InteractiveConsole(sess_vars).interact(banner=FMT_DEBUG + prompt + FMT_NONE, exitmsg="Exiting debugger")
finally:
cpu.debugger_active = False

@ -17,6 +17,9 @@ class RiscemuBaseException(BaseException):
def message(self):
pass
def print_stacktrace(self):
import traceback
traceback.print_exception(type(self), self, self.__traceback__)
# Parsing exceptions:
@ -115,13 +118,15 @@ class InvalidAllocationException(RiscemuBaseException):
class UnimplementedInstruction(RiscemuBaseException):
def __init__(self, ins: 'Instruction'):
def __init__(self, ins: 'Instruction', context = None):
self.ins = ins
self.context = context
def message(self):
return FMT_CPU + "{}({})".format(
return FMT_CPU + "{}({}{})".format(
self.__class__.__name__,
repr(self.ins)
repr(self.ins),
', context={}'.format(self.context) if self.context is not None else ''
) + FMT_NONE

@ -1,4 +1,4 @@
from .InstructionSet import InstructionSet, Instruction
from .instruction_set import InstructionSet, Instruction
from ..exceptions import INS_NOT_IMPLEMENTED
from ..helpers import int_from_bytes, int_to_bytes, to_unsigned, to_signed

@ -4,7 +4,8 @@ RiscEmu (c) 2021 Anton Lydike
SPDX-License-Identifier: MIT
"""
from .InstructionSet import *
from .instruction_set import *
from ..CPU import UserModeCPU
from ..helpers import int_from_bytes, int_to_bytes, to_unsigned, to_signed
from ..colors import FMT_DEBUG, FMT_NONE
@ -116,14 +117,8 @@ class RV32I(InstructionSet):
)
def instruction_add(self, ins: 'Instruction'):
dst = ""
if self.cpu.conf.add_accept_imm:
try:
dst, rs1, rs2 = self.parse_rd_rs_imm(ins)
except:
pass
if not dst:
dst, rs1, rs2 = self.parse_rd_rs_rs(ins)
# 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,
@ -292,20 +287,19 @@ class RV32I(InstructionSet):
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)
self.cpu.syscall_int.handle_syscall(syscall)
def instruction_sbreak(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 0)
if self.cpu.active_debug:
print(FMT_DEBUG + "Debug instruction encountered at 0x{:08X}".format(self.pc - 1) + FMT_NONE)
raise LaunchDebuggerException()
launch_debug_session(
self.cpu,
self.mmu,
self.regs,
"Debug instruction encountered at 0x{:08X}".format(self.pc - 1)
)
print(FMT_DEBUG + "Debug instruction encountered at 0x{:08X}".format(self.pc - 1) + FMT_NONE)
raise LaunchDebuggerException()
def instruction_nop(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 0)

@ -4,7 +4,7 @@ RiscEmu (c) 2021 Anton Lydike
SPDX-License-Identifier: MIT
"""
from .InstructionSet import *
from .instruction_set import *
from ..exceptions import INS_NOT_IMPLEMENTED

@ -6,7 +6,7 @@ SPDX-License-Identifier: MIT
This package holds all instruction sets, available to the processor
"""
from .InstructionSet import InstructionSet
from .instruction_set import InstructionSet
from .RV32M import RV32M
from .RV32I import RV32I
from .RV32A import RV32A

@ -53,6 +53,7 @@ def parse_tokens(name: str, tokens_iter: Iterable[Token]) -> Program:
for token, args in composite_tokenizer(Peekable[Token](tokens_iter)):
if token.type not in PARSERS:
raise ParseException("Unexpected token type: {}, {}".format(token, args))
print('{}: {}'.format(token, args))
PARSERS[token.type](token, args, context)
return context.finalize()
@ -73,6 +74,8 @@ def composite_tokenizer(tokens_iter: Iterable[Token]) -> Iterable[Tuple[Token, T
token = next(tokens)
if token.type in (TokenType.PSEUDO_OP, TokenType.LABEL, TokenType.INSTRUCTION_NAME):
yield token, tuple(take_arguments(tokens))
else:
print("skipped {}".format(token))
def take_arguments(tokens: Peekable[Token]) -> Iterable[str]:
@ -85,12 +88,14 @@ def take_arguments(tokens: Peekable[Token]) -> Iterable[str]:
while True:
if tokens.peek().type == TokenType.ARGUMENT:
yield next(tokens).value
if tokens.peek().type == TokenType.COMMA:
next(tokens)
elif tokens.peek().type == TokenType.NEWLINE:
next(tokens)
break
break
elif tokens.peek().type == TokenType.COMMA:
next(tokens)
else:
break
# raise ParseException("Expected newline, instead got {}".format(tokens.peek()))

@ -16,7 +16,7 @@ from ..instructions import RV32A, RV32M
if typing.TYPE_CHECKING:
from riscemu import types, LoadedExecutable, LoadedInstruction
from riscemu.instructions.InstructionSet import InstructionSet
from riscemu.instructions.instruction_set import InstructionSet
class PrivCPU(CPU):
@ -83,7 +83,7 @@ class PrivCPU(CPU):
self.launch_debug = False
launch_debug_session(self, self.mmu, self.regs,
"Launching debugger:")
if not self.active_debug:
if not self.debugger_active:
self._run(verbose)
else:
print()

@ -1,28 +1,23 @@
"""
RiscEmu (c) 2021 Anton Lydike
RiscEmu (c) 2021-2022 Anton Lydike
SPDX-License-Identifier: MIT
"""
from .config import RunConfig
from .helpers import *
from collections import defaultdict
from .exceptions import InvalidRegisterException
from .helpers import *
class Registers:
"""
Represents a bunch of registers
"""
def __init__(self, conf: RunConfig):
"""
Initialize the register configuration, respecting the RunConfig conf
:param conf: The RunConfig
"""
def __init__(self):
self.vals = defaultdict(lambda: 0)
self.last_set = None
self.last_read = None
self.conf = conf
def dump(self, full=False):
"""
@ -96,7 +91,7 @@ class Registers:
"""
if reg == 'zero':
return False
#if reg not in Registers.all_registers():
# if reg not in Registers.all_registers():
# raise InvalidRegisterException(reg)
# replace fp register with s1, as these are the same register
if reg == 'fp':
@ -114,7 +109,7 @@ class Registers:
:param mark_read: If the register should be markes as "last read" (only used internally)
:return: The contents of register reg
"""
#if reg not in Registers.all_registers():
# if reg not in Registers.all_registers():
# raise InvalidRegisterException(reg)
if reg == 'fp':
reg = 's0'

@ -15,7 +15,7 @@ import riscemu
import typing
if typing.TYPE_CHECKING:
from . import CPU
from riscemu.CPU import UserModeCPU
SYSCALLS = {
63: 'read',
@ -43,7 +43,7 @@ class Syscall:
"""
id: int
"""The syscall number (e.g. 64 - write)"""
cpu: 'riscemu.CPU'
cpu: 'UserModeCPU'
"""The CPU object that created the syscall"""
@property
@ -146,7 +146,8 @@ class SyscallInterface:
Requires running with flag scall-fs
"""
if not scall.cpu.conf.scall_fs:
# FIXME: this should be toggleable in a global setting or somethign
if True:
print(FMT_SYSCALL + '[Syscall] open: opening files not supported without scall-fs flag!' + FMT_NONE)
return scall.ret(-1)
@ -193,7 +194,7 @@ class SyscallInterface:
"""
Exit syscall. Exits the system with status code a0
"""
scall.cpu.exit = True
scall.cpu.halted = True
scall.cpu.exit_code = scall.cpu.regs.get('a0')
def __repr__(self):

@ -14,7 +14,7 @@ from .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-z]+[0-9]{0,2})\)$')
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

@ -9,16 +9,20 @@ See base.py for some basic implementations of these classes
"""
import os
import re
import typing
from abc import ABC, abstractmethod
from collections import defaultdict
from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple, Set, Union, Generator, Iterator, Callable, Type
from typing import Dict, List, Optional, Tuple, Set, Union, Iterator, Callable, Type
from . import MMU, InstructionSet
from .assembler import get_section_base_name
from .colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE, FMT_PARSE, FMT_RED, FMT_BOLD
from .colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE, FMT_RED, FMT_BOLD
from .exceptions import ParseException
from .helpers import format_bytes
from .helpers import format_bytes, get_section_base_name
from .registers import Registers
if typing.TYPE_CHECKING:
from .MMU import MMU
from .instructions.instruction_set import InstructionSet
# define some base type aliases so we can keep track of absolute and relative addresses
T_RelativeAddress = int
@ -231,7 +235,7 @@ class Program:
self.sections = []
self.global_labels = set()
self.base = base
self.loaded = False
self.is_loaded = False
def add_section(self, sec: MemorySection):
# print a warning when a section is located before the programs base
@ -286,7 +290,7 @@ class Program:
for sec in self.sections:
sec.base += at_addr
if self.base != at_addr:
if self.base is not None and self.base != at_addr:
# move sections so they are located where they want to be located
offset = at_addr - self.base
for sec in self.sections:
@ -354,7 +358,8 @@ class CPU(ABC):
INS_XLEN: int = 4
# housekeeping variables
mmu: MMU
regs: Registers
mmu: 'MMU'
pc: T_AbsoluteAddress
cycle: int
halted: bool
@ -364,10 +369,11 @@ class CPU(ABC):
# instruction information
instructions: Dict[str, Callable[[Instruction], None]]
instruction_sets: Set[InstructionSet]
instruction_sets: Set['InstructionSet']
def __init__(self, mmu: MMU, instruction_sets: List[Type[InstructionSet]]):
def __init__(self, mmu: 'MMU', instruction_sets: List[Type['InstructionSet']]):
self.mmu = mmu
self.regs = Registers()
self.instruction_sets = set()
self.instructions = dict()
@ -377,6 +383,7 @@ class CPU(ABC):
self.instructions.update(ins_set.load())
self.instruction_sets.add(ins_set)
self.halted = False
self.cycle = 0
self.pc = 0
self.debugger_active = False
@ -410,3 +417,19 @@ class CPU(ABC):
self.halted,
" ".join(s.name for s in self.instruction_sets)
)
@abstractmethod
def step(self, verbose=False):
pass
@abstractmethod
def run(self, verbose=False):
pass
def launch(self, program: Program, verbose: bool = False):
if program not in self.mmu.programs:
print(FMT_RED + '[CPU] Cannot launch program that\'s not loaded!' + FMT_NONE)
return
self.pc = program.entrypoint
self.run(verbose)

Loading…
Cancel
Save