"""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 ---- # load/store encoding (ground truth: AS860 .S<->.O pairs + DNC.O): # - integer loads (0x00/0x01 ld.b, 0x04/0x05 ld.s|ld.l): dest=bits20:16; # ODD = 16-bit immediate offset, EVEN = register-indexed EA=src2+src1. # op 4/5 size flag = instr bit0 (0=.s 16-bit, 1=.l 32-bit). # - integer stores (0x03 st.b, 0x07 st.s|st.l): source reg = src1 (bits15:11), # offset SPLIT high=bits20:16 low=bits10:0; bit0 size flag as loads. # - FP fld (0x08/0x09) / fst (0x0a/0x0b): FP reg = dest field for BOTH; # ODD = flat s16 imm, EVEN = indexed. Flags: bit0=auto-increment, # bit1: 1=.l, 0=.d; bit2 (with bit1=0) = .q. MEM = {0x00, 0x01, 0x03, 0x04, 0x05, 0x07, 0x08, 0x09, 0x0a, 0x0b} 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: if op in (0x00, 0x01, 0x04, 0x05): # integer loads m = "ld.b" if op < 0x04 else ("ld.l" if (w & 1) else "ld.s") if op & 1: mask = 0xffff if op == 0x01 else (0xfffc if (w & 1) else 0xfffe) return m, f"{s16(imm & mask):#x}({REG[src2]}),{REG[dest]}" return m, f"{REG[src1]}({REG[src2]}),{REG[dest]}" if op in (0x03, 0x07): # integer stores (split offset) off = ((w >> 16) & 0x1f) << 11 | (w & 0x7ff) if op == 0x03: return "st.b", f"{REG[src1]},{s16(off):#x}({REG[src2]})" m = "st.l" if (off & 1) else "st.s" off &= 0xfffc if (off & 1) else 0xfffe return m, f"{REG[src1]},{s16(off):#x}({REG[src2]})" # FP fld/fst: FP reg in dest field; flags bit0=auto++, bit1:1=.l/0=.d, bit2=.q fl = w & 7 sz = ".l" if (fl & 2) else (".q" if (fl & 4) else ".d") pp = "++" if (fl & 1) else "" m = ("fld" if op < 0x0a else "fst") + sz if op & 1: size = 4 if (fl & 2) else (16 if (fl & 4) else 8) ea = f"{s16(imm & (0x10000 - size)):#x}({REG[src2]}){pp}" else: ea = f"{REG[src1]}({REG[src2]}){pp}" if op < 0x0a: return m, f"{ea},{FREG[dest]}" return m, f"{FREG[dest]},{ea}" 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 5 bits (1=lock, 2=calli, 4=intovr, 7=unlock) sub = w & 0x1f if sub == 0x02: return "calli", f"{REG[src1]}" if sub == 0x01: return "lock", "" if sub == 0x07: return "unlock", "" if sub == 0x04: return "intovr", "" return "esc", f"{sub:#x}" 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 == 0x2d: # bla isrc1,isrc2,sbroff (split offset like bte) off = s16((((w >> 16) & 0x1f) << 11 | (w & 0x7ff)) & 0xffff) return "bla", f"{REG[src1]},{REG[src2]},{addr + 4 + off*4:#010x}" 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 ")