diff options
Diffstat (limited to 'nandgame/assembler/py_nand_ass/disas.py')
| -rwxr-xr-x | nandgame/assembler/py_nand_ass/disas.py | 201 |
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}" |
