diff options
| -rw-r--r-- | atk16_asm/asm_ops.py | 48 | ||||
| -rw-r--r-- | atk16_asm/parser.py | 138 | ||||
| -rw-r--r-- | atk16_emu/cli.py | 21 | ||||
| -rw-r--r-- | atk16_emu/debugger.py | 98 | ||||
| -rw-r--r-- | atk16_emu/emu.py | 8 | ||||
| -rw-r--r-- | test/e2e/fibo/fibo.atk16 | 57 | ||||
| -rw-r--r-- | test/e2e/fibo/test_fibo.py | 2 |
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 |
