aboutsummaryrefslogtreecommitdiffstats
path: root/atk16_emu
diff options
context:
space:
mode:
authorJan Tuomi <jans.tuomi@gmail.com>2024-02-25 14:45:36 +0200
committerJan Tuomi <jans.tuomi@gmail.com>2024-02-25 14:45:36 +0200
commit93c07558e5209d608c6f1d833507db4cb1579ad7 (patch)
treece75695221b3c4f4c0220db76548720cb95776b8 /atk16_emu
parent8fc27a6e08529e8b4d7af54ba67e49bed71f61be (diff)
Fix stuff, add debugger colors
Diffstat (limited to 'atk16_emu')
-rw-r--r--atk16_emu/colors.py10
-rw-r--r--atk16_emu/debugger.py133
-rw-r--r--atk16_emu/emu.py74
3 files changed, 186 insertions, 31 deletions
diff --git a/atk16_emu/colors.py b/atk16_emu/colors.py
new file mode 100644
index 0000000..2264d4b
--- /dev/null
+++ b/atk16_emu/colors.py
@@ -0,0 +1,10 @@
+class C:
+ HEADER = '\033[95m'
+ OKBLUE = '\033[94m'
+ OKCYAN = '\033[96m'
+ OKGREEN = '\033[92m'
+ WARNING = '\033[93m'
+ FAIL = '\033[91m'
+ ENDC = '\033[0m'
+ BOLD = '\033[1m'
+ UNDERLINE = '\033[4m'
diff --git a/atk16_emu/debugger.py b/atk16_emu/debugger.py
index b2183c1..86dbcbe 100644
--- a/atk16_emu/debugger.py
+++ b/atk16_emu/debugger.py
@@ -1,6 +1,7 @@
from getch import getche
from .emu import Machine
from dataclasses import dataclass
+from .colors import C
@dataclass
class DbgAddrInfo:
@@ -20,6 +21,8 @@ def try_parse_num(s: str) -> int | None:
return None
class Debugger:
+ HISTORY_MAX_LEN = 1000
+
def __init__(self):
self.rom_image = None
self.breakpoints: set[int] = set()
@@ -28,6 +31,11 @@ class Debugger:
self.machine.run()
self.dbg_addr_info: dict[int, DbgAddrInfo] = {}
+ self.time_traveling_enabled = False
+ # Index complement, 0 is last element
+ self.history_cmpl_index = 0
+ self.history_buffer: list[Machine] = []
+
def load_rom_image(self, rom_image_path: str) -> None:
self.rom_image_path = rom_image_path
with open(rom_image_path, "rb") as f:
@@ -71,10 +79,10 @@ class Debugger:
labels=labels,
)
- print(f"Loaded debug info for from \"{dbg_symbols_path}\".")
+ print(f"Loaded debug info from \"{dbg_symbols_path}\".")
def query_breakpoint(self) -> int | None:
- input_ = input("Breakpoint address: ")
+ input_ = input(C.UNDERLINE + "Breakpoint address:" + C.ENDC + " ")
as_number = try_parse_num(input_)
if input_ == "":
return None
@@ -100,43 +108,65 @@ class Debugger:
def print_breakpoints(self):
for addr in self.breakpoints:
if addr in self.dbg_addr_info and len(self.dbg_addr_info[addr].labels) > 0:
- label_suffix = f" [{' '.join(self.dbg_addr_info[addr].labels)}] ({self.dbg_addr_info[addr].src_info})"
+ label_suffix = " "
+ label_suffix += C.OKCYAN + f"[{' '.join(self.dbg_addr_info[addr].labels)}] " + C.ENDC
+ label_suffix += f"({self.dbg_addr_info[addr].src_info})"
else:
label_suffix = ""
- print(f" 0x{addr:>04x}{label_suffix}")
+
+ addr_prefix = C.OKGREEN + f"0x{addr:>04x}" + C.ENDC
+ print(f" {addr_prefix}{label_suffix}")
if len(self.breakpoints) == 0:
- print("<no breakpoints>")
+ print(" <no breakpoints>")
print()
def print_pc_context(self):
- print("=== Program context")
+ print(C.UNDERLINE + "Program context" + C.ENDC)
for i in range(-4, 5):
addr = self.machine.pc.value + i
if addr < 0 or addr >= 64 * 2 ** 16:
continue
+ addr_prefix = C.OKGREEN + f"0x{addr:>04x}" + C.ENDC
+ word_infix = C.OKBLUE + f"0x{self.machine.mem_read(addr):>04x}" + C.ENDC
if i == 0:
- print(f"> 0x{addr:>04x}: 0x{self.machine.mem_read(addr):>04x}", end="")
+ print(f"> {addr_prefix}: {word_infix}", end="")
else:
- print(f" 0x{addr:>04x}: 0x{self.machine.mem_read(addr):>04x}", end="")
+ print(f" {addr_prefix}: {word_infix}", end="")
if addr in self.dbg_addr_info:
dbg_info = self.dbg_addr_info[addr]
- labels: str = f"[{' '.join(dbg_info.labels)}] " if len(dbg_info.labels) > 0 else ""
- print(f" {dbg_info.text:<{self.dbg_text_col_width}} {dbg_info.original_text:<{self.dbg_original_text_col_width}} {labels}({dbg_info.src_info})")
+ labels: str = C.OKCYAN + f"[{' '.join(dbg_info.labels)}] " + C.ENDC if len(dbg_info.labels) > 0 else ""
+ dbg_text = f"{dbg_info.text:<{self.dbg_text_col_width}}"
+ dbg_orig = f"{dbg_info.original_text:<{self.dbg_original_text_col_width}}"
+ print(f" {dbg_text} {dbg_orig} {labels}({dbg_info.src_info})")
else:
print()
print()
+ def record_history(self):
+ self.history_buffer.append(self.machine.make_copy())
+ if len(self.history_buffer) > Debugger.HISTORY_MAX_LEN:
+ self.history_buffer.pop(0)
+
+ def reset(self):
+ self.machine.reset()
+ self.machine.run()
+ self.history_buffer = []
+ self.history_cmpl_index = 0
+
def activate(self):
- print("=== ATK16 debugger ===")
+ print(C.WARNING + "=== ATK16 debugger ===" + C.ENDC)
print("Press ? to show command help. Press q to quit.")
print()
while True:
+ if self.history_cmpl_index > 0:
+ print(C.OKGREEN + f"Currently {self.history_cmpl_index} steps into history" + C.ENDC + "\n")
+
self.print_pc_context()
print("dbg> ", end="", flush=True)
cmd = getche()
@@ -155,28 +185,55 @@ class Debugger:
self.load_rom_image(load_path)
- self.machine.reset()
- self.machine.run()
+ self.reset()
print("Machine reset.")
elif cmd == "r":
+ if self.time_traveling_enabled:
+ if self.history_cmpl_index - 1 > 0:
+ self.history_cmpl_index -= 1
+ self.machine = self.history_buffer[len(self.history_buffer) - self.history_cmpl_index]
+ continue
+
+ if self.history_cmpl_index == 1:
+ self.history_cmpl_index = 0
+ self.history_buffer.pop()
+
while self.machine.running:
+ if self.time_traveling_enabled:
+ self.record_history()
+
self.machine.step()
+
if self.machine.pc.value in self.breakpoints:
for addr in self.breakpoints:
if addr in self.dbg_addr_info and len(self.dbg_addr_info[addr].labels) > 0:
- label_suffix = f" [{' '.join(self.dbg_addr_info[addr].labels)}] ({self.dbg_addr_info[addr].src_info})"
+ label_infix = C.OKCYAN + f"[{' '.join(self.dbg_addr_info[addr].labels)}]" + C.ENDC
+ label_suffix = f" {label_infix} ({self.dbg_addr_info[addr].src_info})"
else:
label_suffix = ""
- print(f"Breakpoint hit at 0x{self.machine.pc.value:>04x}{label_suffix}")
+ pc_hex = C.OKGREEN + f"0x{self.machine.pc.value:>04x}" + C.ENDC
+ print(C.WARNING + f"Breakpoint hit at " + C.ENDC + f"{pc_hex}{label_suffix}\n")
break
if not self.machine.running:
- print("Machine halted.")
+ print(C.FAIL + "Machine halted." + C.ENDC)
elif cmd == "b":
- print("Set breakpoints:")
+ if not self.time_traveling_enabled:
+ print(C.WARNING + "Time traveling mode is not enabled." + C.ENDC + "\n")
+ continue
+
+ if self.history_cmpl_index == len(self.history_buffer):
+ print(C.WARNING + "No more history available" + C.ENDC + "\n")
+ continue
+
+ self.history_cmpl_index += 1
+ self.machine = self.history_buffer[len(self.history_buffer) - self.history_cmpl_index]
+
+ elif cmd == "B":
+ print(C.UNDERLINE + "Active breakpoints:" + C.ENDC)
self.print_breakpoints()
addr = self.query_breakpoint()
@@ -187,37 +244,65 @@ class Debugger:
if type(addr) != int or addr < 0 or addr >= 64 * 2 ** 16:
print("Invalid address")
+ addr_hex = C.OKGREEN + f"0x{addr:>04x}" + C.ENDC
if addr in self.breakpoints:
self.breakpoints.remove(addr)
- print(f"Removed breakpoint at 0x{addr:>04x}")
+ print(C.WARNING + f"Removed breakpoint at {addr_hex}" + C.ENDC)
else:
self.breakpoints.add(addr)
+ print(C.WARNING + f"Set breakpoint at {addr_hex}" + C.ENDC)
+
+ print()
elif cmd == "n":
+ if self.time_traveling_enabled:
+ if self.history_cmpl_index - 1 > 0:
+ self.history_cmpl_index -= 1
+ self.machine = self.history_buffer[len(self.history_buffer) - self.history_cmpl_index]
+ continue
+
+ if self.history_cmpl_index == 1:
+ self.history_cmpl_index = 0
+ self.history_buffer.pop()
+
if not self.machine.running:
- print("Machine halted.")
+ print(C.FAIL + "Machine halted." + C.ENDC)
continue
+
+ if self.time_traveling_enabled:
+ self.record_history()
+
self.machine.step()
+
if not self.machine.running:
- print("Machine halted.")
+ print(C.FAIL + "Machine halted." + C.ENDC)
elif cmd == "s":
self.machine.print_state_summary()
elif cmd == "0":
- self.machine.reset()
- self.machine.run()
+ self.reset()
print("Machine reset.")
+ elif cmd == "t":
+ self.time_traveling_enabled = not self.time_traveling_enabled
+
+ if not self.time_traveling_enabled:
+ self.history_buffer = []
+ self.history_cmpl_index = 0
+
+ print(C.WARNING + f"Time traveling mode: {'on' if self.time_traveling_enabled else 'off'}" + C.ENDC + "\n")
+
else:
print(f"Unknown command: {cmd}")
def print_help(self):
print("Debugger commands:")
+ print(" t toggle time traveling mode (slow)")
print(" r run until next breakpoint or until halted")
print(" n step forward")
- print(" b step backward")
- print(" b set or remove breakpoint")
+ print(" b step backward (if in time traveling mode)")
+ print(" B set or remove breakpoint")
print(" s show state summary")
print(" 0 reset machine state")
print(" l load ROM image and reset machine state")
diff --git a/atk16_emu/emu.py b/atk16_emu/emu.py
index 8eeb6b1..c87fff6 100644
--- a/atk16_emu/emu.py
+++ b/atk16_emu/emu.py
@@ -3,6 +3,7 @@ from typing import Literal
from dataclasses import dataclass
import sys
from .opcodes import *
+from .colors import C
class Register:
def __init__(self, bits: int):
@@ -141,6 +142,55 @@ class Machine:
self.running = False
+ def make_copy(self):
+ new_machine = Machine()
+
+ new_machine.rom = ROM(self.rom.addr_bits, self.rom.data_bits)
+ new_machine.rom.memory = self.rom.memory.copy()
+
+ new_machine.ram = RAM(self.ram.addr_bits, self.ram.data_bits)
+ new_machine.ram.memory = self.ram.memory.copy()
+
+ new_machine.alu = self.alu
+
+ new_machine.ra = Register(self.ra.bits)
+ new_machine.ra.value = self.ra.value
+
+ new_machine.rb = Register(self.rb.bits)
+ new_machine.rb.value = self.rb.value
+
+ new_machine.rc = Register(self.rc.bits)
+ new_machine.rc.value = self.rc.value
+
+ new_machine.rd = Register(self.rd.bits)
+ new_machine.rd.value = self.rd.value
+
+ new_machine.re = Register(self.re.bits)
+ new_machine.re.value = self.re.value
+
+ new_machine.rf = Register(self.rf.bits)
+ new_machine.rf.value = self.rf.value
+
+ new_machine.rg = Register(self.rg.bits)
+ new_machine.rg.value = self.rg.value
+
+ new_machine.rh = Register(self.rh.bits)
+ new_machine.rh.value = self.rh.value
+
+ new_machine.pc = Counter(self.pc.bits)
+ new_machine.pc.value = self.pc.value
+
+ new_machine.fr = ALUFlags(
+ carry = self.fr.carry,
+ overflow = self.fr.overflow,
+ zero = self.fr.zero,
+ sign = self.fr.sign,
+ )
+
+ new_machine.running = self.running
+
+ return new_machine
+
def mem_read(self, addr: int):
if addr < 2 ** 15:
return self.rom.read(addr & 0x7FFF)
@@ -332,15 +382,25 @@ class Machine:
raise ValueError(f"Invalid instruction: {instr:>016b} ({instr:>04x})")
def print_state_summary(self):
+ pc_hex = C.OKBLUE + f"0x{self.pc.value:>04x}" + C.ENDC
+ print(f"PC: {pc_hex} ({self.pc.value})")
+
for i in range(8):
reg_name = f"r{chr(ord('a') + i)}"
value = getattr(self, reg_name).value
- print(f"{reg_name.upper()}: 0x{value:>04x} ({value})")
+ reg_hex = C.OKBLUE + f"0x{value:>04x}" + C.ENDC
+ print(f"{reg_name.upper()}: {reg_hex} ({value})")
- print(f"PC: 0x{self.pc.value:>04x} ({self.pc.value})")
- print(f"FR: carry={self.fr.carry}\n "
- f"overflow={self.fr.overflow}\n "
- f"zero={self.fr.zero}\n "
- f"sign={self.fr.sign}")
for i in range(8):
- print(f"RAM[{i}]: 0x{self.ram.read(i):>04x} ({self.ram.read(i)})") \ No newline at end of file
+ ram_hex = C.OKBLUE + f"0x{self.ram.read(i):>04x}" + C.ENDC
+ print(f"RAM[{i}]: {ram_hex} ({self.ram.read(i)})")
+
+ def as_num(b: bool) -> str:
+ return ((C.OKGREEN + "1") if b else (C.WARNING + "0")) + C.ENDC
+
+ print(f"FR: C: {as_num(self.fr.carry)}, "
+ f"O: {as_num(self.fr.overflow)}, "
+ f"Z: {as_num(self.fr.zero)}, "
+ f"S: {as_num(self.fr.sign)}")
+
+ print() \ No newline at end of file