sh-dis/python/emulator/simulate.py
Zack Buhman 8a300ba4c6 initial SH4 emulator implementation in C
This currently only implements the SH2 instructions.
2024-04-22 20:53:36 +08:00

355 lines
9.7 KiB
Python

import curses
import curses.panel
from curses.textpad import Textbox, rectangle
import time
from dataclasses import dataclass
import sys
from emulator.sh2 import SH2
from emulator.impl import ILLSLOT, TRAP
from emulator.mem import Memory
from emulator.execute import step
from emulator.operations import sign_extend32
from emulator.log import get_log, log, raw_log
from decode import decode_instruction, decode_variables
last_seen1 = {}
last_seen = {}
@dataclass
class C:
color: curses.color_pair
value: str
def get_color(name, value, update):
global last_seen
global last_seen1
if not update:
seen = last_seen1
else:
seen = last_seen
if name not in seen or seen[name] != value:
color = curses.color_pair(5)
else:
color = curses.color_pair(0)
if update:
if name in last_seen:
last_seen1[name] = last_seen[name]
last_seen[name] = value
return color
def render_registers(stdscr, cpu, simulator, update):
others = [
'sr',
'gbr',
'vbr',
'mach',
'macl',
'pr',
'pc',
]
def format_value(value):
if simulator.display_mode == "int":
return f"{sign_extend32(value)}"
if simulator.display_mode == "uint":
return f"{value}"
elif simulator.display_mode == "hex":
return f"0x{value:08x}"
else:
assert False, simulator.display_mode
for i in range(8):
ra = i
rb = i+8
stdscr.addstr(2 + i, 4 + 0 , f"r{ra}")
color = get_color(ra, cpu.reg[ra], update)
stdscr.addstr(2 + i, 4 + 5 , format_value(cpu.reg[ra]), color)
stdscr.addstr(2 + i, 4 + 20, f"r{rb}")
color = get_color(rb, cpu.reg[rb], update)
stdscr.addstr(2 + i, 4 + 25, format_value(cpu.reg[rb]), color)
if i < len(others):
name = others[i]
if i == 0:
sr = cpu.sr
value = f"m:{sr.m} q:{sr.q} i:{sr.i} s:{sr.s} t:{sr.t}"
else:
value = getattr(cpu, name)
value = format_value(value)
color = get_color(name, value, update)
stdscr.addstr(2 + i, 4 + 40, f"{name}")
stdscr.addstr(2 + i, 4 + 45, value, color)
def format_variables(ins, variables):
s = ins.operands
for i, name in enumerate(ins.variables):
if 'disp' in s and name == 'd':
name = 'disp'
if 'imm' in s and name == 'i':
name = 'imm'
if 'label' in s and name == 'd':
name = 'label'
s = s.replace(name, str(variables[i]))
return s
def render_instructions(stdscr, cpu, mem):
color0 = curses.color_pair(1)
color1 = curses.color_pair(2)
color2 = curses.color_pair(3)
y = 12
x = 4
history_length = 12
address_offset = (cpu.pc + 4) - ((history_length // 2) * 2)
i = 0
while i < history_length:
address = address_offset + (i * 2)
if address == cpu.pc:
stdscr.addstr(y+i, x+0 , f">")
if address >= 0:
stdscr.addstr(y+i, x+2 , f"{address:08x}")
instruction = mem.read16(address)
ins = decode_instruction(instruction)
label = f"0x{instruction:04x}"
if ins is None:
operation = ""
operands = ""
else:
variables = decode_variables(instruction, ins)
operation = ins.instruction
operands = format_variables(ins, variables)
ins_len = len('CMP/STR ')
stdscr.addstr(y+i, x+12 + 0 , f"{label}", color0)
stdscr.addstr(y+i, x+12 + 8 , f"{operation}", color1)
stdscr.addstr(y+i, x+12 + 8 + ins_len , f"{operands}", color2)
i += 1
def render_log(stdscr):
_log = get_log()
y, x = stdscr.getmaxyx()
log_length = max(3, ((y - 4) - 26))
for i in range(log_length):
ix = (log_length - i)
if ix > len(_log):
continue
entry = _log[-ix]
if type(entry) is str:
stdscr.addstr(26 + i, 4 + 0, entry)
else:
assert type(entry) is tuple
x_offset = 0
for e in entry:
if type(e) is C:
color, s = e.color, e.value
stdscr.addstr(26 + i, 4 + x_offset, s, color)
else:
s = e
stdscr.addstr(26 + i, 4 + x_offset, s)
x_offset += len(s) + 1
def render(stdscr, cpu, mem, simulator, update):
stdscr.clear()
render_registers(stdscr, cpu, simulator, update)
render_instructions(stdscr, cpu, mem)
render_log(stdscr)
y, x = stdscr.getmaxyx()
message = "backspace: Ctrl-H"
stdscr.addstr(y-4, x - (len(message) + 3), message, curses.color_pair(6))
rectangle(stdscr, (y-3), 2, (y-3)+1+1, x - (2 + 1))
stdscr.refresh()
def log_buf(buf, buf_start):
s = " ".join(f'{n:02x}' for n in buf)
address = f'{buf_start:08x}'
string = repr("".join(chr(n) for n in buf))
raw_log(
address,
':',
C(curses.color_pair(1), s), # green
';',
C(curses.color_pair(4), string) # magenta
)
@dataclass
class Simulator:
break_points: list[int]
start_time: int
instructions: int
free_run: bool
display_mode: str
def __init__(self):
self.break_points = []
self.start_time = time.monotonic()
self.instructions = 0
self.free_run = False
self.display_mode = "hex"
def set_continue(self):
self.start_time = time.monotonic()
self.instructions = 0
self.free_run = True
def check_break_points(self, cpu):
if not self.free_run:
return
for address in self.break_points:
if address != cpu.pc:
continue
self.free_run = False
end = time.monotonic()
log("time", end - self.start_time)
log("instructions", self.instructions)
return
def print_memory(mem, start, end):
i = start
buf = []
buf_start = start
while True:
b = mem.read8(i)
buf.append(b)
if len(buf) >= 8:
log_buf(buf, buf_start)
buf = []
buf_start = i + 1
if i == end:
break
i += 1
if buf:
log_buf(buf, buf_start)
def parse_registers(args):
regs = []
for arg in args:
arg = arg.lower()
if not arg.startswith('@r'):
raise ValueError(arg)
n = int(arg.removeprefix('@r'), 10)
if n >= 0 and n <= 16:
regs.append(n)
else:
raise ValueError(arg)
return regs
def handle_command(line, simulator, cpu, mem):
cmd, *args = line.split()
if cmd == "p" or cmd == "print" or cmd == "p/x":
if not args:
log("print: missing argument")
return
try:
if args[0].startswith('@'):
regs = parse_registers(args)
start = cpu.reg[regs[0]]
end = cpu.reg[regs[1]] if len(regs) >= 2 else start
else:
start = int(args[0], 16)
end = int(args[1], 16) if len(args) >= 2 else start
except ValueError:
log("print: invalid arguments", args)
return
print_memory(mem, start, end)
elif cmd == "b" or cmd == "break":
if not args:
log("break: missing argument")
return
address = int(args[0], 16)
if address % 2 != 0:
log("break: invalid address", f'{address:08x}')
else:
simulator.break_points.append(address)
log("break: added", f'{address:08x}')
elif cmd == "c" or cmd == "continue":
simulator.set_continue()
elif cmd == "hex":
simulator.display_mode = "hex"
elif cmd == "int":
simulator.display_mode = "int"
elif cmd == "uint":
simulator.display_mode = "uint"
else:
log("unknown command", repr(cmd), args)
def main(stdscr):
curses.start_color()
curses.use_default_colors()
curses.init_pair(1, curses.COLOR_GREEN, -1)
curses.init_pair(2, curses.COLOR_CYAN, -1)
curses.init_pair(3, curses.COLOR_RED, -1)
curses.init_pair(4, curses.COLOR_MAGENTA, -1)
curses.init_pair(5, curses.COLOR_YELLOW, -1)
curses.init_pair(6, 246, -1) # GREY
with open(sys.argv[1], 'rb') as f:
_buf = f.read()
buf = memoryview(_buf)
cpu = SH2()
mem = Memory()
simulator = Simulator()
for address, b in enumerate(buf):
mem.write8(address, b)
curses.curs_set(0) # invisible cursor
render(stdscr, cpu, mem, simulator, True)
def new_inputbox():
y, x = stdscr.getmaxyx()
nlines = 1
ncols = x - (4 * 2 + 1)
editwin = curses.newwin(nlines, ncols, y-2, 4)
editwin.move(0, 0)
box = Textbox(editwin)
return box
def next_input():
box = new_inputbox()
curses.curs_set(1)
box.edit()
curses.curs_set(0)
command = box.gather().rstrip()
if command == '':
return True
else:
handle_command(command, simulator, cpu, mem)
return False
while True:
update = False
if simulator.free_run or next_input():
try:
step(cpu, mem)
except TRAP as e:
simulator.free_run = 0
log("trap", e.args[0])
simulator.instructions += 1
update = True
simulator.check_break_points(cpu)
if not simulator.free_run or simulator.instructions % 100 == 0:
render(stdscr, cpu, mem, simulator, update)
curses.wrapper(main)