aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile5
-rw-r--r--atk16_asm/asm_pass0.py7
-rw-r--r--atk16_asm/asm_pass3.py35
-rw-r--r--atk16_asm/asm_pass4.py9
-rwxr-xr-xatk16_asm/assembler.py36
-rw-r--r--atk16_emu/cli.py11
-rw-r--r--atk16_emu/debugger.py107
7 files changed, 179 insertions, 31 deletions
diff --git a/Makefile b/Makefile
index d045622..06999ab 100644
--- a/Makefile
+++ b/Makefile
@@ -48,10 +48,7 @@ test:
pytest --ignore resources
install:
- mkdir -p "$(PREFIX)/share/atk16/asm"
- cp atk16_asm/*.py "$(PREFIX)/share/atk16/asm/"
- printf "#!/bin/sh\ncd \"$(PREFIX)/share/atk16\"\n/usr/bin/env python -m asm.assembler \$$@\n" > "$(PREFIX)/bin/atk16c"
- chmod +x "$(PREFIX)/bin/atk16c"
+ pip install .
run-single-test:
java -cp $(digital_path) CLI test -verbose -circ $(circ) -tests $(tests)
diff --git a/atk16_asm/asm_pass0.py b/atk16_asm/asm_pass0.py
index 262e5c1..f6586cd 100644
--- a/atk16_asm/asm_pass0.py
+++ b/atk16_asm/asm_pass0.py
@@ -15,9 +15,12 @@ class Result0:
lines: list[Result0Line]
def pass_0(lines: list[str], file_name: str) -> Result0:
+ file_name = file_name if file_name.endswith(".atk16") else file_name + ".atk16"
result_lines: list[Result0Line] = []
for (line_num, line) in enumerate(lines):
+ line_num += 1 # line numbers are 1-based
+
line = line.split(";")[0].strip()
if line == "": continue
@@ -25,8 +28,8 @@ def pass_0(lines: list[str], file_name: str) -> Result0:
match keyword:
case "@include":
- asm_file_name = args[0]
- path = os.path.join(os.path.dirname(file_name), asm_file_name + ".atk16")
+ asm_file_name = args[0] if args[0].endswith(".atk16") else args[0] + ".atk16"
+ path = os.path.join(os.path.dirname(file_name), asm_file_name)
with open(path, "r") as f:
incl_lines = f.readlines()
diff --git a/atk16_asm/asm_pass3.py b/atk16_asm/asm_pass3.py
index 09f58fa..ec8b20c 100644
--- a/atk16_asm/asm_pass3.py
+++ b/atk16_asm/asm_pass3.py
@@ -12,15 +12,43 @@ class Result3Line:
original_parts: list[str]
@dataclass
+class DbgSourceInfo:
+ src_file: str
+ line_num: int
+ expanded_text: str
+ original_text: str
+
+DbgLabelTable = dict[int, list[str]]
+DbgSourceTable = dict[int, DbgSourceInfo]
+
+@dataclass
class Result3:
lines: list[Result3Line]
operations: OpExpansionDict
symbols: dict[str, int]
+ dbg_label_table: DbgLabelTable
+ dbg_source_table: DbgSourceTable
def pass_3(result2: Result2) -> Result3:
result_lines: list[Result3Line] = []
symbols: dict[str, int] = {}
address = 0
+ dbg_label_table: DbgLabelTable = {}
+ dbg_source_table: DbgSourceTable = {}
+
+ def save_dbg_label(addr: int, label: str):
+ if addr in dbg_label_table:
+ dbg_label_table[addr].append(label)
+ else:
+ dbg_label_table[addr] = [label]
+
+ def save_dbg_source(addr: int, src_file: str, line_num: int, expanded_text: str, original_text: str):
+ dbg_source_table[addr] = DbgSourceInfo(
+ src_file=src_file,
+ line_num=line_num,
+ expanded_text=expanded_text,
+ original_text=original_text,
+ )
for line in result2.lines:
keyword, *args = line.parts
@@ -33,11 +61,15 @@ def pass_3(result2: Result2) -> Result3:
if label in symbols:
raise Exception(f"When defining label {label} as {address:>04x}, symbol {label} already defined as {symbols[label]:>04x}")
symbols[args[0]] = address
+ save_dbg_label(address, label)
continue
case "@let":
symbols[args[0]] = eval_expr(symbols, args[1])
continue
case _:
+ dbg_original_text = " ".join(line.original_parts)
+ dbg_expanded_text = " ".join(line.parts)
+ save_dbg_source(address, line.src_file, line.line_num, dbg_expanded_text, dbg_original_text)
result_lines.append(Result3Line(
line_num=line.line_num,
src_file=line.src_file,
@@ -54,10 +86,13 @@ def pass_3(result2: Result2) -> Result3:
if n > 1:
formatted = format_overlapping_rows(rows_with_same_addr)
raise Exception(f"Overlapping segments: address 0x{result_line.address:>04x} has conflicting definitions:\n{formatted}")
+
return Result3(
operations=result2.operations,
lines=result_lines,
symbols=symbols,
+ dbg_label_table=dbg_label_table,
+ dbg_source_table=dbg_source_table,
)
def format_overlapping_rows(rows: list[Result3Line]) -> str:
diff --git a/atk16_asm/asm_pass4.py b/atk16_asm/asm_pass4.py
index 7365416..52aaf68 100644
--- a/atk16_asm/asm_pass4.py
+++ b/atk16_asm/asm_pass4.py
@@ -17,6 +17,8 @@ class Result4:
lines: list[Result4Line]
operations: OpExpansionDict
symbols: dict[str, int]
+ dbg_label_table: DbgLabelTable
+ dbg_source_table: DbgSourceTable
def pass_4(result3: Result3) -> Result4:
result_lines: list[Result4Line] = []
@@ -32,7 +34,10 @@ def pass_4(result3: Result3) -> Result4:
if keyword in operations:
fn = operations[keyword]
- word = fn(meta, result3.symbols, *args)
+ try:
+ word = fn(meta, result3.symbols, *args)
+ except Exception as e:
+ raise Exception(f"Invalid assembly at {line.src_file}:{line.line_num + 1}\n\n{line.parts}\n\n{e}") from e
result_lines.append(Result4Line(
line_num=line.line_num,
src_file=line.src_file,
@@ -58,4 +63,6 @@ def pass_4(result3: Result3) -> Result4:
operations=result3.operations,
symbols=result3.symbols,
lines=result_lines,
+ dbg_label_table=result3.dbg_label_table,
+ dbg_source_table=result3.dbg_source_table,
)
diff --git a/atk16_asm/assembler.py b/atk16_asm/assembler.py
index 993472e..4cd2cef 100755
--- a/atk16_asm/assembler.py
+++ b/atk16_asm/assembler.py
@@ -7,11 +7,17 @@ from .asm_eval import *
from .asm_pass0 import pass_0
from .asm_pass1 import pass_1
from .asm_pass2 import pass_2
-from .asm_pass3 import pass_3
+from .asm_pass3 import pass_3, DbgLabelTable, DbgSourceTable
from .asm_pass4 import pass_4
from .pad_bin import pad_binary
-def assemble(source: str, file_name: str) -> bytearray:
+@dataclass
+class AssemblyResult:
+ program: bytearray
+ dbg_label_table: DbgLabelTable
+ dbg_source_table: DbgSourceTable
+
+def assemble(source: str, file_name: str) -> AssemblyResult:
src_lines = source.splitlines()
result0 = pass_0(src_lines, file_name)
@@ -40,7 +46,11 @@ def assemble(source: str, file_name: str) -> bytearray:
result[2 * line.address + 0] = ((line.word >> 8) & 0xff)
result[2 * line.address + 1] = ((line.word >> 0) & 0xff)
- return result
+ return AssemblyResult(
+ program=result,
+ dbg_label_table=result4.dbg_label_table,
+ dbg_source_table=result4.dbg_source_table,
+ )
def main():
if len(sys.argv) != 3:
@@ -62,11 +72,27 @@ def main():
result = assemble(src, infile_path)
with open(outfile_path, "wb") as f:
- f.write(result)
+ f.write(result.program)
pad_binary(outfile_path, 64 * 1024)
- print(f"Wrote 64 KB to {outfile_path} ({len(result)} B without padding)")
+ print(f"Wrote 64 KB to {outfile_path} ({len(result.program)} B without padding)")
+
+ dbg_outfile_path = outfile_path + ".dbg"
+
+ with open(dbg_outfile_path, "w") as f:
+ for i in range(32 * 1024): # 32K words
+ line = f"0x{i:>04x};"
+ if i not in result.dbg_source_table:
+ continue
+
+ dbg_source_info = result.dbg_source_table[i]
+ line += f"{dbg_source_info.src_file}:{dbg_source_info.line_num};{dbg_source_info.expanded_text};{dbg_source_info.original_text};"
+
+ if i in result.dbg_label_table:
+ line += f"{';'.join(result.dbg_label_table[i])}"
+
+ f.write(line + "\n")
if __name__ == "__main__":
main()
diff --git a/atk16_emu/cli.py b/atk16_emu/cli.py
index 824fbbf..fcea56b 100644
--- a/atk16_emu/cli.py
+++ b/atk16_emu/cli.py
@@ -39,13 +39,10 @@ def main():
print_help()
sys.exit(1)
- def load_rom_image_from_path(path: str) -> bytearray:
- with open(path, "rb") as f:
- return bytearray(f.read())
-
- rom_image = load_rom_image_from_path(options.rom_image_path)
-
if not options.debugger_enabled:
+ with open(options.rom_image_path, "rb") as f:
+ rom_image = bytearray(f.read())
+
machine = Machine()
machine.load_rom_image(rom_image)
machine.reset()
@@ -58,7 +55,7 @@ def main():
else:
debugger = Debugger()
- debugger.load_rom_image(rom_image)
+ debugger.load_rom_image(options.rom_image_path)
debugger.activate()
if __name__ == "__main__":
diff --git a/atk16_emu/debugger.py b/atk16_emu/debugger.py
index 3d8f131..a1fd470 100644
--- a/atk16_emu/debugger.py
+++ b/atk16_emu/debugger.py
@@ -1,5 +1,13 @@
from getch import getche
from .emu import Machine
+from dataclasses import dataclass
+
+@dataclass
+class DbgAddrInfo:
+ src_info: str
+ text: str
+ original_text: str
+ labels: list[str]
class Debugger:
def __init__(self):
@@ -8,12 +16,50 @@ class Debugger:
self.machine = Machine()
self.machine.reset()
self.machine.run()
+ self.dbg_addr_info: dict[int, DbgAddrInfo] = {}
+
+ 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:
+ self.rom_image = bytearray(f.read())
+
+ self.machine.load_rom_image(self.rom_image)
+
+ print(f"Loaded ROM image from \"{rom_image_path}\".")
+
+ dbg_symbols_path = rom_image_path + ".dbg"
+ try:
+ with open(dbg_symbols_path, "r") as f:
+ dbg_file_lines = f.readlines()
+ except FileNotFoundError:
+ print(f"Note: debug symbols file \"{dbg_symbols_path}\" not found.")
+ return
+
+ self.dbg_addr_info = {}
+ self.dbg_text_col_width = 0
+ self.dbg_original_text_col_width = 0
+ for line in dbg_file_lines:
+ addr, src_info, text, original_text, *labels_ = line.split(";")
+ addr = int(addr, 16)
+
+ if len(text) > self.dbg_text_col_width:
+ self.dbg_text_col_width = len(text)
- def load_rom_image(self, rom_image: bytearray) -> None:
- self.rom_image = rom_image
- self.machine.load_rom_image(rom_image)
+ if len(original_text) > self.dbg_original_text_col_width:
+ self.dbg_original_text_col_width = len(original_text)
- print("Loaded rom image.")
+ labels = []
+ for label in labels_:
+ label = label.strip()
+ if label != "":
+ labels.append(label)
+
+ self.dbg_addr_info[addr] = DbgAddrInfo(
+ src_info=src_info,
+ text=text,
+ original_text=original_text,
+ labels=labels,
+ )
def print_pc_context(self):
print("=== Program context")
@@ -23,9 +69,16 @@ class Debugger:
continue
if i == 0:
- print(f"> 0x{addr:>04x}: 0x{self.machine.mem_read(addr):>04x}")
+ print(f"> 0x{addr:>04x}: 0x{self.machine.mem_read(addr):>04x}", end="")
+ else:
+ print(f" 0x{addr:>04x}: 0x{self.machine.mem_read(addr):>04x}", 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})")
else:
- print(f" 0x{addr:>04x}: 0x{self.machine.mem_read(addr):>04x}")
+ print()
print()
@@ -42,13 +95,32 @@ class Debugger:
if cmd == "q":
break
+
elif cmd == "?":
self.print_help()
+
+ elif cmd == "l":
+ load_path = input(f"Path to rom image [default: {self.rom_image_path}]: ")
+ if load_path.strip() == "":
+ load_path = self.rom_image_path
+
+ self.load_rom_image(load_path)
+
+ self.machine.reset()
+ self.machine.run()
+ print("Machine reset.")
+
elif cmd == "r":
while self.machine.running:
self.machine.step()
if self.machine.pc.value in self.breakpoints:
- print(f"Breakpoint hit at 0x{self.machine.pc.value:>04x}")
+ 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)}]"
+ else:
+ label_suffix = ""
+
+ print(f"Breakpoint hit at 0x{self.machine.pc.value:>04x}{label_suffix}")
break
if not self.machine.running:
@@ -57,13 +129,21 @@ class Debugger:
elif cmd == "b":
print("Set breakpoints:")
for addr in self.breakpoints:
- print(f" 0x{addr:>04x}")
+ 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)}]"
+ else:
+ label_suffix = ""
+ print(f" 0x{addr:>04x}{label_suffix}")
if len(self.breakpoints) == 0:
print("<no breakpoints>")
print()
try:
- addr = eval(input("Breakpoint address: "), {})
+ env: dict[str, int] = {}
+ for addr in self.dbg_addr_info:
+ for label in self.dbg_addr_info[addr].labels:
+ env[label] = addr
+ addr = eval(input("Breakpoint address: "), env)
except:
print("Cancelled")
continue
@@ -75,21 +155,23 @@ class Debugger:
print(f"Removed breakpoint at 0x{addr:>04x}")
else:
self.breakpoints.add(addr)
+
elif cmd == "n":
if not self.machine.running:
print("Machine halted.")
continue
-
self.machine.step()
-
if not self.machine.running:
print("Machine halted.")
+
elif cmd == "s":
self.machine.print_state_summary()
+
elif cmd == "0":
self.machine.reset()
self.machine.run()
print("Machine reset.")
+
else:
print(f"Unknown command: {cmd}")
@@ -100,7 +182,8 @@ class Debugger:
print(" b step backward")
print(" b set or remove breakpoint")
print(" s show state summary")
- print(" 0 reset the machine state")
+ print(" 0 reset machine state")
+ print(" l load ROM image and reset machine state")
print(" q quit")
print(" ? show this help")
print()