summaryrefslogtreecommitdiff
path: root/nandgame/assembler/py_nand_ass/disas.py
diff options
context:
space:
mode:
authoruvok2026-01-14 20:44:16 +0100
committeruvok2026-01-14 20:44:16 +0100
commit1561eff8780dc15dc5ea46d7225cc49a46f709ca (patch)
tree130d44ef295ff2113fc56c592a78780035449dff /nandgame/assembler/py_nand_ass/disas.py
parent281414ea9b42e213b85b95b7072b73d1f1e3f240 (diff)
Restructure asembler as package
Diffstat (limited to 'nandgame/assembler/py_nand_ass/disas.py')
-rwxr-xr-xnandgame/assembler/py_nand_ass/disas.py201
1 files changed, 201 insertions, 0 deletions
diff --git a/nandgame/assembler/py_nand_ass/disas.py b/nandgame/assembler/py_nand_ass/disas.py
new file mode 100755
index 0000000..116aea4
--- /dev/null
+++ b/nandgame/assembler/py_nand_ass/disas.py
@@ -0,0 +1,201 @@
+#!/usr/bin/env python3
+
+"""
+Disassembler for nandgame.
+
+Using my own flavor of assembly language.
+I don't like the "C-style" one nandgame introduces.
+"""
+
+import sys
+
+ZERO = "#0"
+DEST_NONE = "_"
+JUMP_NONE = ""
+JUMPS_IF_NZERO = ["jgt", "jlt", "jne", "jmp"]
+JUMPS_IF_ZERO = ["jge", "jle", "jeq", "jmp"]
+
+ENDIANNESS = "little"
+
+
+def decode_jump(ins: int) -> str:
+ if (ins & 0x7) == 0:
+ return JUMP_NONE
+
+ if (ins & 0x7) == 0x7:
+ return "jmp"
+
+ jl = (ins & (1 << 2)) != 0
+ je = (ins & (1 << 1)) != 0
+ jg = (ins & (1 << 0)) != 0
+
+ # implied: and not jg
+ if jl and je:
+ return "jle"
+ # implied: and not je
+ if jl and jg:
+ return "jne"
+ # implied: and not je
+ if je and jg:
+ return "jge"
+
+ # implied: only one flag is 1
+ if jl:
+ return "jlt"
+ if je:
+ return "jeq"
+ if jg:
+ return "jgt"
+
+ return "<unknown>"
+
+
+# return op, and whether it's a one-op or two-op
+def decode_ins(ins: int) -> tuple[str, bool]:
+ opcode = (ins >> 8) & 0x03
+ ar_n_log = (ins & (1 << 10)) != 0
+ opcode |= ar_n_log << 2
+
+ if opcode == 0b000:
+ return "and", True
+ if opcode == 0b001:
+ return "or", True
+ if opcode == 0b010:
+ return "xor", True
+ if opcode == 0b011:
+ return "not", False
+ if opcode == 0b100:
+ return "add", True
+ if opcode == 0b101:
+ return "inc", False
+ if opcode == 0b110:
+ return "sub", True
+ if opcode == 0b111:
+ return "dec", False
+
+ return "<?>", False
+
+
+# normally, X = arg1 = D
+def decode_arg1(ins: int) -> str:
+ use_mem = (ins & (1 << 12)) != 0
+ zx = (ins & (1 << 7)) != 0
+ sw = (ins & (1 << 6)) != 0
+
+ if zx:
+ return ZERO
+ if not sw:
+ return "D"
+ return "M" if use_mem else "A"
+
+
+# normally, Y = arg2 = A
+def decode_arg2(ins: int) -> str:
+ use_mem = (ins & (1 << 12)) != 0
+
+ # don't care, only X is zeroed
+ # zx = (ins & (1 << 7)) != 0
+ sw = (ins & (1 << 6)) != 0
+
+ if sw:
+ return "D"
+ return "M" if use_mem else "A"
+
+
+def decode_dest(ins: int) -> str:
+ dA = (ins & (1 << 5)) != 0
+ dD = (ins & (1 << 4)) != 0
+ dM = (ins & (1 << 3)) != 0
+ dest = ""
+ if dA:
+ dest += "A"
+ if dD:
+ dest += "D"
+ if dM:
+ dest += "M"
+
+ return dest if dest else DEST_NONE
+
+
+def decode_instruction(ins: int) -> list[str]:
+ """
+ Will return a 5 element list/tuple/whatever
+ mnemonic, destination, X, Y, jumpdest
+ """
+ if ins & 0x8000 == 0:
+ # mov? ldr? ldi? aaaaaaaaaaa....
+ return ["mov", "A", f"#{ins}", "", ""]
+ else:
+ mnemonic, two_op = decode_ins(ins)
+ dest = decode_dest(ins)
+ op1 = decode_arg1(ins)
+ op2 = decode_arg2(ins) if two_op else ""
+ jumpdest = decode_jump(ins)
+ return [mnemonic, dest, op1, op2, jumpdest]
+
+
+def fixup_ins(ins: int) -> list[str]:
+ (mnemonic, dest, op1, op2, jumpdest) = decode_instruction(ins)
+ # fixups
+ if op1 == ZERO:
+ # subtract something from #0 - subtraction
+ if mnemonic == "sub":
+ return ["neg", dest, op2, "", jumpdest]
+
+ # 0 AND something = 0
+ if mnemonic == "and":
+ # if no dest, only jump matters
+ if dest == DEST_NONE:
+ # jump always or jump-if-zero --> always jump
+ if jumpdest in JUMPS_IF_ZERO:
+ return ["jmp", "", "", "", ""]
+ # all other jumps? <, >, <>, nojmp
+ else:
+ return ["nop", "", "", "", ""]
+ else:
+ if jumpdest in JUMPS_IF_ZERO:
+ newjmp = "jmp"
+ else:
+ newjmp = ""
+ return ["mov", dest, ZERO, "", newjmp]
+
+ # 0 +/|/^ something = something
+ if mnemonic in ["add", "or", "xor"]:
+ if dest == DEST_NONE and jumpdest == JUMP_NONE:
+ return ["nop", "", "", "", ""]
+ else:
+ return ["mov", dest, op2, "", jumpdest]
+
+ # basically, not 0 == 0xFFFF....
+ # opposite of what AND is doing?
+ if mnemonic == "not":
+ # if no dest, only jump matters
+ if dest == DEST_NONE:
+ # 0xFFFF is not jgt, since highest bit is always signed.
+ if jumpdest in ["jeq", "jgt", "jge", JUMP_NONE]:
+ return ["nop", "", "", "", ""]
+ else:
+ return ["jmp", "", "", "", ""]
+ elif dest == DEST_NONE:
+ if mnemonic == "sub":
+ return ["cmp", "", op1, op2, jumpdest]
+
+ return [mnemonic, dest, op1, op2, jumpdest]
+
+
+def print_decoded(ins: int, simplify: bool) -> str:
+ # illegal instruction
+ if ins & 0xC000 == 0x8000 and simplify:
+ return "halt"
+
+ if simplify:
+ (mnemonic, dest, op1, op2, jumpdest) = fixup_ins(ins)
+ else:
+ (mnemonic, dest, op1, op2, jumpdest) = decode_instruction(ins)
+
+ jumpdest_str = f".{jumpdest}" if jumpdest else ""
+ opcode_str = f"{mnemonic}{jumpdest_str}"
+ dest_str = f"{dest}, " if dest else 7 * " "
+ op2_str = ", " if op2 else ""
+ op1_str = f"{op1}{op2_str}"
+ return f"{opcode_str:<9}{dest_str:<6}{op1_str:<4}{op2}"