aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--atk16_asm/asm_ops.py48
-rw-r--r--atk16_asm/parser.py138
-rw-r--r--atk16_emu/cli.py21
-rw-r--r--atk16_emu/debugger.py98
-rw-r--r--atk16_emu/emu.py8
-rw-r--r--test/e2e/fibo/fibo.atk1657
-rw-r--r--test/e2e/fibo/test_fibo.py2
7 files changed, 303 insertions, 69 deletions
diff --git a/atk16_asm/asm_ops.py b/atk16_asm/asm_ops.py
index 394a643..293b1cc 100644
--- a/atk16_asm/asm_ops.py
+++ b/atk16_asm/asm_ops.py
@@ -10,6 +10,13 @@ OpExpansionDict = dict[str, ExpandFn]
class Meta:
address: int
+generate_unique_label_counter = 0
+def generate_unique_label(prefix: str) -> str:
+ global generate_unique_label_counter
+ label = f"{prefix}_{generate_unique_label_counter}"
+ generate_unique_label_counter += 1
+ return label
+
def make_alr(meta: Meta, symbols: Symbols, alu_op: str, left: str, right: str, target: str) -> int:
"""ALR 0000 TTTL LLRR RSSS"""
target_e = eval_expr(symbols, target, bits=3)
@@ -215,25 +222,25 @@ def expand_sinc(imm: str) -> ExpandResult:
def expand_sdec(imm: str) -> ExpandResult:
return expand_subi("SP", imm, "SP")
-def expand_csr(addr_reg: str, stratch_reg: str) -> ExpandResult:
+def expand_callr(addr_reg: str) -> ExpandResult:
return [
- ["lpc", stratch_reg],
- *expand_addi(stratch_reg, "4", stratch_reg),
- *expand_spu(stratch_reg),
+ ["lpc", "RG"],
+ *expand_addi("RG", "4", "RG"),
+ *expand_spu("RG"),
["jpr", addr_reg]
]
-def expand_csi(addr_imm: str, stratch_reg: str) -> ExpandResult:
+def expand_calli(addr_imm: str) -> ExpandResult:
+ ret_label = generate_unique_label(prefix="calli_return")
return [
- ["lpc", stratch_reg],
- *expand_addi(stratch_reg, "4", stratch_reg),
- *expand_spu(stratch_reg),
- ["jpi", addr_imm]
+ ["ldi", ret_label, "RG"],
+ *expand_spu("RG"),
+ ["jpi", addr_imm],
+ ["@label", ret_label]
]
-def expand_rsr(stratch_reg: str) -> ExpandResult:
- return expand_spo(stratch_reg) + [["jpr", stratch_reg]]
-
+def expand_return() -> ExpandResult:
+ return expand_spo("RG") + [["jpr", "RG"]]
def stack_stash(*rs: str) -> ExpandResult:
result: ExpandResult = []
@@ -245,17 +252,17 @@ def stack_stash(*rs: str) -> ExpandResult:
def stack_restore(*rs: str) -> ExpandResult:
result: ExpandResult = []
for r in rs:
- prefix = expand_spu(r)
+ prefix = expand_spo(r)
result = prefix + result
return result
def set_graphics_mode(mode: str) -> ExpandResult:
return [
- ["ldi", "vt_gr_mode_addr", "RA"],
- ["ldr", "RA", "RA"],
- ["ldi", mode, "RB"],
- ["str", "RB", "RA"],
+ ["ldi", "vt_gr_mode_addr", "RF"],
+ ["ldr", "RF", "RF"],
+ ["ldi", mode, "RG"],
+ ["str", "RG", "RF"],
]
expansions: OpExpansionDict = {
@@ -298,11 +305,10 @@ expansions: OpExpansionDict = {
"spo": expand_spo,
"sinc": expand_sinc,
"sdec": expand_sdec,
- "csr": expand_csr,
- "csi": expand_csi,
- "rsr": expand_rsr,
+ "callr": expand_callr,
+ "calli": expand_calli,
+ "return": expand_return,
"stack_stash": stack_stash,
"stack_restore": stack_restore,
"set_graphics_mode": set_graphics_mode,
}
-
diff --git a/atk16_asm/parser.py b/atk16_asm/parser.py
new file mode 100644
index 0000000..4823450
--- /dev/null
+++ b/atk16_asm/parser.py
@@ -0,0 +1,138 @@
+from typing import Callable, Literal, cast
+from dataclasses import dataclass
+import re
+
+RegisterValue = Literal["RA", "RB", "RC", "RD", "RE", "RF", "RG", "RH"]
+REGISTERS: list[RegisterValue] = ["RA", "RB", "RC", "RD", "RE", "RF", "RG", "RH"]
+@dataclass
+class RegisterRef:
+ """Reference to a register, e.g. RA"""
+ reg: RegisterValue
+
+ def __str__(self) -> str:
+ return self.reg
+
+@dataclass
+class Immediate:
+ """Immediate value, e.g. 0x1234"""
+ value: int
+ base: Literal["dec", "hex", "bin"]
+
+ def __str__(self) -> str:
+ if self.base == "dec":
+ return str(self.value)
+ elif self.base == "hex":
+ return f"0x{self.value:>04x}"
+ elif self.base == "bin":
+ return f"0b{self.value:>016b}"
+
+ raise Exception("Invalid base: {self.base}")
+
+@dataclass
+class Label:
+ """Label declaration, e.g. @loop"""
+ label: str
+
+ def __str__(self) -> str:
+ return f"@{self.label}"
+
+@dataclass
+class LabelRef:
+ """Label reference, e.g. &loop"""
+ label: str
+
+ def __str__(self) -> str:
+ return f"&{self.label}"
+
+FlagValue = Literal[0, 1, 2, 3]
+FLAGS = ["carry", "overflow", "zero", "sign"]
+@dataclass
+class Flag:
+ """ALU Flag, e.g. zero"""
+ num: FlagValue
+
+ @staticmethod
+ def from_str(flag: str) -> "Flag":
+ return Flag(cast(FlagValue, FLAGS.index(flag)))
+
+ def __str__(self) -> str:
+ return FLAGS[self.num]
+
+ALUCodeValue = Literal[0, 1, 2, 3, 4, 5, 6, 7]
+ALU_CODES = ["al_plus", "al_minus", "al_and", "al_or", "al_xor", "al_sll", "al_slr", "al_sar"]
+@dataclass
+class ALUCode:
+ """ALU operation code, e.g. al_plus"""
+ code: ALUCodeValue
+
+ @staticmethod
+ def from_str(code: str) -> "ALUCode":
+ return ALUCode(cast(ALUCodeValue, ALU_CODES.index(code)))
+
+ def __str__(self) -> str:
+ return ALU_CODES[self.code]
+
+@dataclass
+class Expr:
+ """Python expression that can be evaluated to an immediate value"""
+ expr: str
+
+ def __str__(self) -> str:
+ return f"${{{self.expr}}}"
+
+@dataclass
+class Directive:
+ """Directive, e.g. #address"""
+ directive: str
+
+ def __str__(self) -> str:
+ return f"#${self.directive}"
+
+@dataclass
+class StringLiteral:
+ """String literal, e.g. "hello world" """
+ string: str
+
+ def __str__(self) -> str:
+ return f'"{self.string}"'
+
+@dataclass
+class Symbol:
+ """Symbol, e.g. loop"""
+ symbol: str
+
+ def __str__(self) -> str:
+ return self.symbol
+
+Term = RegisterRef | Immediate | LabelRef | Label | Flag | ALUCode | Expr | Directive | StringLiteral | Symbol
+
+symbol_pattern = "[a-zA-Z_][a-zA-Z0-9_]*"
+
+def parse(tokens: list[str]) -> list[Term]:
+ def parse_token(term: str, idx: int) -> Term:
+ if term in REGISTERS:
+ return RegisterRef(term)
+ elif term in FLAGS:
+ return Flag.from_str(term)
+ elif term.startswith("${") and term.endswith("}"):
+ return Expr(term[2:-1])
+ elif term.startswith("\"") and term.endswith("\""):
+ return StringLiteral(term[1:-1])
+ elif term.startswith("&"):
+ return LabelRef(term[1:])
+ elif term.startswith("@"):
+ return Label(term[1:])
+ elif term.startswith("#"):
+ return Directive(term[1:])
+ elif term.startswith("0x"):
+ return Immediate(int(term.replace("_", ""), 16), "hex")
+ elif term.startswith("0b"):
+ return Immediate(int(term.replace("_", ""), 2), "bin")
+ elif term.isdigit():
+ return Immediate(int(term.replace("_", ""), 10), "dec")
+ elif re.match(symbol_pattern, term) is not None:
+ return Symbol(term)
+ else:
+ raise Exception(f"Invalid term \"{term}\" at index {idx} in {tokens}")
+
+ return [parse_token(token, idx) for idx, token in enumerate(tokens)]
diff --git a/atk16_emu/cli.py b/atk16_emu/cli.py
index 7aa4b6b..d6bd46b 100644
--- a/atk16_emu/cli.py
+++ b/atk16_emu/cli.py
@@ -1,6 +1,7 @@
import sys
from dataclasses import dataclass
from .emu import Machine
+from .debugger import Debugger
@dataclass
class Options:
@@ -43,12 +44,18 @@ def load_rom_image_from_path(path: str) -> bytearray:
rom_image = load_rom_image_from_path(options.rom_image_path)
-machine = Machine()
-machine.load_rom_image(rom_image)
-machine.reset()
-machine.run_until_halted()
+if not options.debugger_enabled:
+ machine = Machine()
+ machine.load_rom_image(rom_image)
+ machine.reset()
+ machine.run_until_halted()
-print("Machine halted.")
-print("===============")
+ print("Machine halted.")
+ print("===============")
-machine.print_state_summary()
+ machine.print_state_summary()
+
+else:
+ debugger = Debugger()
+ debugger.load_rom_image(rom_image)
+ debugger.activate()
diff --git a/atk16_emu/debugger.py b/atk16_emu/debugger.py
new file mode 100644
index 0000000..f887ed0
--- /dev/null
+++ b/atk16_emu/debugger.py
@@ -0,0 +1,98 @@
+from getch import getche
+from .emu import Machine
+
+class Debugger:
+ def __init__(self):
+ self.rom_image = None
+ self.breakpoints: set[int] = set()
+ self.machine = Machine()
+ self.machine.reset()
+ self.machine.run()
+
+ def load_rom_image(self, rom_image: bytearray) -> None:
+ self.rom_image = rom_image
+ self.machine.load_rom_image(rom_image)
+
+ print("Loaded rom image.")
+
+ def print_pc_context(self):
+ print("=== Program context")
+ for i in range(-4, 5):
+ addr = self.machine.pc.value + i
+ if addr < 0 or addr >= 64 * 2 ** 16:
+ continue
+
+ if i == 0:
+ print(f"> 0x{addr:>04x}: 0x{self.machine.mem_read(addr):>04x}")
+ else:
+ print(f" 0x{addr:>04x}: 0x{self.machine.mem_read(addr):>04x}")
+
+ print()
+
+ def activate(self):
+ print("=== ATK16 debugger ===")
+ print("Press ? to show command help. Press q to quit.")
+ print()
+
+ while True:
+ self.print_pc_context()
+ print("dbg> ", end="", flush=True)
+ cmd = getche()
+ print()
+
+ if cmd == "q":
+ break
+ elif cmd == "?":
+ self.print_help()
+ 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}")
+ break
+
+ if not self.machine.running:
+ print("Machine halted.")
+
+ elif cmd == "b":
+ print("Set breakpoints:")
+ for addr in self.breakpoints:
+ print(f" 0x{addr:>04x}")
+ if len(self.breakpoints) == 0:
+ print("<no breakpoints>")
+ print()
+
+ try:
+ addr = eval(input("Breakpoint address: "), {})
+ except:
+ print("Cancelled")
+ continue
+ if type(addr) != int or addr < 0 or addr >= 64 * 2 ** 16:
+ print("Invalid address")
+
+ if addr in self.breakpoints:
+ self.breakpoints.remove(addr)
+ print(f"Removed breakpoint at 0x{addr:>04x}")
+ else:
+ self.breakpoints.add(addr)
+ elif cmd == "n":
+ self.machine.step()
+ elif cmd == "s":
+ self.machine.print_state_summary()
+ elif cmd == "0":
+ self.machine.reset()
+ print("Machine reset.")
+ else:
+ print(f"Unknown command: {cmd}")
+
+ def print_help(self):
+ print("Debugger commands:")
+ print(" r run until next breakpoint or until halted")
+ print(" n step forward")
+ print(" b step backward")
+ print(" b set or remove breakpoint")
+ print(" s show state summary")
+ print(" 0 reset the machine state")
+ print(" q quit")
+ print(" ? show this help")
+ print()
diff --git a/atk16_emu/emu.py b/atk16_emu/emu.py
index 591eeb5..8eeb6b1 100644
--- a/atk16_emu/emu.py
+++ b/atk16_emu/emu.py
@@ -208,6 +208,8 @@ class Machine:
instr = self.mem_read(pc_addr)
instruction = self.decode(instr)
+ #print(f"Executing instruction 0b{instr:>016b} (0x{instr:>04x}) at address 0x{pc_addr:>04x}")
+
try:
match instruction:
case ALR(target, left, right, alu_code):
@@ -254,6 +256,8 @@ class Machine:
self.pc.value = addr
case JPI(imm):
+ # convert imm from signed (twos complement) 9-bit to a python int
+ imm = (imm & (0b011111111)) - (imm & 0b100000000)
self.pc.value = (self.pc.value + imm) & 0xFFFF
case BRR(flag, addr_reg):
@@ -262,6 +266,8 @@ class Machine:
self.pc.value = addr
case BRI(flag, addr_imm):
+ # convert imm from signed (twos complement) 9-bit to a python int
+ addr_imm = (addr_imm & (0b011111111)) - (addr_imm & 0b100000000)
if self.check_nth_flag(flag):
self.pc.value = (self.pc.value + addr_imm) & 0xFFFF
@@ -288,7 +294,7 @@ class Machine:
self.running = False
except:
- print(f"Error while executing instruction {instr:>016b} ({instr:>04x}) at address {pc_addr:>04x}", file=sys.stderr)
+ print(f"Error while executing instruction 0b{instr:>016b} (0x{instr:>04x}) at address 0x{pc_addr:>04x}", file=sys.stderr)
raise
def decode(self, instr: int):
diff --git a/test/e2e/fibo/fibo.atk16 b/test/e2e/fibo/fibo.atk16
index 608b4d1..fe38fe5 100644
--- a/test/e2e/fibo/fibo.atk16
+++ b/test/e2e/fibo/fibo.atk16
@@ -3,51 +3,30 @@
@include bootstrap
@label main
-; call fibo subroutine with parameter 10
ldi 10 RA
- spush RA
- call fibo
- spop RA
+ calli fibo ; call fibo subroutine with parameter 10
hlt
-@label fibo
-; % n -> % result
+@label fibo ; fibo(n) subroutine
+ ; arguments: n (RA)
+ ; return: fibo(n) (RA)
-; store RB, RC on stack
- spush RB
- spush RC
-; if n < 2, return n
- subi RA 2 RA
- bri sign fibo_early
-; store also RD on stack
- spush RD
-; a = 0
- ldi 0 RB
-; b = 1
- ldi 1 RC
+ stack_stash RB RC RD ; store RB, RC on stack
+ subi RA 2 RB
+ bri sign fibo_early ; if n < 2, return n
+ ldi 0 RB ; a = 0
+ ldi 1 RC ; b = 1
@label fibo_loop
-; v = a + b
- add RB RC RD
-; a = b
- mov RC RB
-; b = v
- mov RD RC
-; n -= 1
- dec RA
-; loop while n > 0
+ add RB RC RD ; v = a + b
+ mov RC RB ; a = b
+ mov RD RC ; b = v
+ dec RA ; n -= 1
bri zero fibo_done
- jpi fibo_loop
+ jpi fibo_loop ; loop while n > 0
@label fibo_early
-; restore used registers
- spop RC
- spop RB
-; return from subroutine
- return
+ stack_restore RB RC RD ; restore used registers
+ return ; return from subroutine
@label fibo_done
- mov RD RA
-; restore used registers
- spop RD
- spop RC
- spop RB
-; return from subroutine
+ mov RD RA ; restore used registers
+ stack_restore RB RC RD ; return from subroutine
return
diff --git a/test/e2e/fibo/test_fibo.py b/test/e2e/fibo/test_fibo.py
index 426435a..6dae09b 100644
--- a/test/e2e/fibo/test_fibo.py
+++ b/test/e2e/fibo/test_fibo.py
@@ -16,4 +16,4 @@ def test_fibo():
machine.run_until_halted()
machine.print_state_summary()
- assert machine.ra.value == 34
+ assert machine.ra.value == 89