"""Intel i860 disassembler for the VelociRender firmware. Opcode/field assignments are those VALIDATED against our own AS860 .S<->.O pairs (see i860-encoding.md / derive860.py); unknowns decode as `.word`. This is also the front-end for an eventual i860 *emulator* that runs VREND.MNG directly. Run `python dis860.py --validate` to check the decoder against the matching source/object pairs (per-instruction base-mnemonic agreement).""" import sys, struct, os, glob REG = [f"r{i}" for i in range(32)] FREG = [f"f{i}" for i in range(32)] CTRL = {0: "fir", 1: "psr", 2: "dirbase", 3: "db", 4: "fsr", 5: "epsr"} def s16(v): return v - 0x10000 if v & 0x8000 else v def s26(v): return v - 0x4000000 if v & 0x2000000 else v # ---- FP escape (op 0x12) sub-opcode[6:0] -> base name (validated where marked) ---- FP_SUB = { 0x00: "r2p1", 0x01: "r2pt", 0x02: "r2ap1", 0x04: "i2p1", 0x05: "i2pt", 0x06: "i2ap1", 0x07: "i2apt", 0x08: "rat1p2", 0x09: "m12apm", 0x0a: "ra1p2", 0x0b: "m12ttpa", 0x0c: "iat1p2", 0x0d: "m12tpm", 0x0e: "ia1p2", 0x0f: "m12tpa", 0x1c: "mimt1s2", 0x20: "fmul", 0x21: "fmlow", 0x22: "frcp", 0x23: "frsqr", 0x24: "fmul3", 0x28: "pfmam", 0x2c: "pfmsm", 0x30: "fadd", 0x31: "fsub", 0x32: "fix", 0x34: "fgt", 0x35: "feq", 0x3a: "ftrunc", 0x40: "fxfr", 0x49: "fiadd", 0x4d: "fisub", 0x50: "faddp", 0x51: "faddz", 0x57: "fzchk", 0x5a: "form", 0x5b: "fzchkl", 0x5f: "fnop", } # precision suffix from result/source-precision bits (bit8=R result-prec, bit7=S src-prec) def fp_suffix(w): s = 'd' if (w >> 7) & 1 else 's' r = 'd' if (w >> 8) & 1 else 's' return f".{s}{r}" def dec_fp(w): sub = w & 0x7f p = (w >> 10) & 1 # P: pipelined src2 = (w >> 21) & 0x1f; dest = (w >> 16) & 0x1f; src1 = (w >> 11) & 0x1f base = FP_SUB.get(sub) if base is None: return (".fp?", f"{sub:#04x} {FREG[src1]},{FREG[src2]},{FREG[dest]}") if base == "fxfr": # FP->int reg move return ("fxfr", f"{FREG[src1]},{REG[dest]}") if base == "fnop": return ("fnop", "") name = ("pf" if p else "f") + base[1:] if base[0] == 'f' else (("pf" if p else "") + base) # graphics dual-ops (r2p1 etc.) keep their name; only f* get the pf/f prefix if base[0] != 'f': name = base return (name + fp_suffix(w), f"{FREG[src1]},{FREG[src2]},{FREG[dest]}") # ---- primary decode ---- # immediate-form rule: for load/store (0x00-0x0b) and arith/logic (0x20-0x3f), # the ODD opcode of each pair is the 16-bit immediate/const form. MEM = {0x00: "ld.s", 0x01: "ld.s", 0x04: "ld.l", 0x05: "ld.l", 0x06: "st.l", 0x07: "st.l", 0x08: "fld.l", 0x09: "fld.l", 0x0a: "fst.l", 0x0b: "fst.l"} ARITH = {0x20: "addu", 0x22: "subu", 0x24: "adds", 0x26: "subs", 0x28: "shl", 0x2a: "shr", 0x2c: "shrd", 0x2e: "shra", 0x30: "and", 0x32: "andh", 0x34: "andnot", 0x36: "andnoth", 0x38: "or", 0x3a: "orh", 0x3c: "xor", 0x3e: "xorh"} CTRLB = {0x1a: "br", 0x1b: "call", 0x1c: "bc", 0x1d: "bc.t", 0x1e: "bnc", 0x1f: "bnc.t"} def decode(w, addr): op = (w >> 26) & 0x3f src2 = (w >> 21) & 0x1f; dest = (w >> 16) & 0x1f; src1 = (w >> 11) & 0x1f imm = w & 0xffff # loads/stores if op in MEM: m = MEM[op] off = s16(imm & 0xfffc) if 'l' in m else s16(imm) if m.startswith('f'): rd = FREG[dest] else: rd = REG[dest] if m.startswith('st') or m.startswith('fst'): return m, f"{rd},{off:#x}({REG[src2]})" return m, f"{off:#x}({REG[src2]}),{rd}" if op == 0x02: return "ixfr", f"{REG[src1]},{FREG[dest]}" if op == 0x0c: return "ld.c", f"{CTRL.get(src2,src2)},{REG[dest]}" if op == 0x0e: return "st.c", f"{REG[src1]},{CTRL.get(src2,src2)}" if op == 0x10: return "bri", f"{REG[src1]}" if op == 0x11: return "trap", f"{REG[src1]},{REG[src2]},{REG[dest]}" if op == 0x12: return dec_fp(w) if op == 0x13: # core escape: sub-op in low bits; calli common if (w & 0x7ff) == 0 or True: return "calli", f"{REG[src1]}" if op in (0x14, 0x15, 0x16, 0x17): base = "btne" if op < 0x16 else "bte" broff = s16(((dest << 11) | (w & 0x7ff)) & 0xffff) tgt = addr + 4 + broff * 4 # odd opcode = 5-bit-const form (i860 rule; e.g. `btne 0x0,r19` = 0x15) s1 = f"{src1:#x}" if op & 1 else f"{REG[src1]}" return base, f"{s1},{REG[src2]},{tgt:#010x}" if op in CTRLB: tgt = addr + 4 + s26(w & 0x03ffffff) * 4 if CTRLB[op] in ("br", "call"): return CTRLB[op], f"{tgt:#010x}" return CTRLB[op], f"{tgt:#010x}" if op in (0x18, 0x19): off = s16(imm & 0xfff8) return "pfld.d", f"{off:#x}({REG[src2]}),{FREG[dest]}" if (op & 0x3e) in ARITH: m = ARITH[op & 0x3e] if op & 1: # immediate form v = s16(imm) return m, f"{v:#x},{REG[src2]},{REG[dest]}" # register form; recognise pseudo-ops (mov/nop = shl r0) if m == "shl" and src1 == 0: if src2 == 0 and dest == 0: return "nop", "" return "mov", f"{REG[src2]},{REG[dest]}" if m == "or" and src1 == 0 and src2 == 0: return "mov", f"r0,{REG[dest]}" return m, f"{REG[src1]},{REG[src2]},{REG[dest]}" return ".word", f"{w:#010x}" # ---------------- validation ---------------- def _norm_ops(s): """normalize an operand string for comparison: strip spaces, lower, and canonicalize hex/dec immediates.""" import re s = s.lower().replace(' ', '') def canon(m): v = int(m.group(0), 0) return str(v) return re.sub(r'-?0x[0-9a-f]+|-?\b\d+\b', canon, s) def validate(): sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import derive860 as D base = r'C:\VWE\TeslaRel410\sda4\DPL3\VRENDER\AS860' # operand check only for ops whose operands are NOT link-time relocated OPCHECK = ('ld.', 'st.', 'fld.', 'fst.', 'adds', 'addu', 'subs', 'subu', 'and', 'or', 'xor', 'shl', 'shr', 'shra', 'mov', 'ixfr', 'bri') for s in sorted(glob.glob(os.path.join(base, '*.S'))): o = s[:-2] + '.O' if not os.path.exists(o): continue labels, instrs = D.parse_S(s) text, syms = D.load_text(o) common = [k for k in labels if k in syms] aligned = not any(labels[k] != syms[k] for k in common) ok = tot = 0; oper_ok = oper_tot = 0; misses = []; omiss = [] for (off, mnem, line) in instrs: if off + 4 > len(text): break w = struct.unpack_from(' len(text): break w = struct.unpack_from(' a = [x for x in sys.argv if not x.startswith('--')][1:] mng = a[0]; start = int(a[1], 0); count = int(a[2]) if len(a) > 2 else 32 print(disasm_range(load_mng(mng), start, count)) else: print("usage: dis860.py --validate | --list ")