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)