sh-dis/simulate.py

355 lines
9.7 KiB
Python

import curses
import curses.panel
from curses.textpad import Textbox, rectangle
import time
from dataclasses import dataclass
from sh2 import SH2
from impl2 import ILLSLOT, TRAP
from mem import Memory
import sys
from execute import step
from decode import decode_instruction, decode_variables
from operations import sign_extend32
from log import get_log, log, raw_log
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)