355 lines
9.7 KiB
Python
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)
|