""" mips_core.py -- R4300i (MIPS III) integer core, every data operation through the mips_units API (golden or neural). Brick 1 scope: full integer ISA with delay slots and branch-likely nullification, 64-bit registers with MIPS III 32-bit sign-extension semantics, HI/LO, COP0 stubs (Count/Compare/Status), big-endian memory through a pluggable bus. No TLB (KSEG0/KSEG1 direct map), no FPU (brick 2), overflow traps treated as non-trapping adds. """ from mips_units import GoldenUnits, ALU, OPC_CLASSES, FUNCT_CLASSES, REGIMM_CLASSES from fpu import SoftFP, F32, F64 M32 = 0xFFFFFFFF M64 = 0xFFFFFFFFFFFFFFFF def sx8(v): return v - 0x100 if v & 0x80 else v def sx16(v): return v - 0x10000 if v & 0x8000 else v def sx32(v): return (v | 0xFFFFFFFF00000000) if v & 0x80000000 else (v & M32) def sx32i(v): return v - 0x100000000 if v & 0x80000000 else v class CPUError(Exception): pass class TLBMiss(Exception): pass class R4300: def __init__(self, bus, units=None): self.bus = bus self.u = units or GoldenUnits() self.alu = ALU(self.u) self.r = [0] * 32 self.hi = self.lo = 0 self.pc = 0; self.next_pc = 4 self.cop0 = {9: 0, 11: 0, 12: 0, 13: 0} # Count/Compare/Status/Cause always present (hot path indexes directly) self.ll_bit = 0 self.instr_count = 0 self.tlb = [None] * 32 # (hi, pagemask, lo0, lo1) self.int_pending = 0 # external lines -> Cause.IP[2..6] # ---- COP1 ---- self.fp = SoftFP(self.alu, self.u) self.fpr = [0] * 32 # 64-bit FPRs self.fcsr = 0 self.fcr0 = 0x00000B00 # R4300 FPU revision # cached direct handle to RDRAM for the hot instruction-fetch path # (the bytearray is mutated in place, never reassigned) self._rdram = bus.rdram self._rdram_len = len(bus.rdram) # ---------- helpers ---------- def set_r(self, i, v): if i: self.r[i] = v & M64 def sgn64(self, v): return v - (1 << 64) if v >> 63 else v def slt_signed(self, a, b): """signed a < b via subtract wiring over verified slices""" sa, sb = a >> 63, b >> 63 if sa != sb: return int(sa == 1) diff, _ = self.alu.sub(a, b, 64) return int(diff >> 63) def slt_unsigned(self, a, b): _, borrow = self.alu.sub(a, b, 64) return borrow def mrd(self, a, n): return self.bus.read(self.translate(a), n) def mwr(self, a, v, n): self.bus.write(self.translate(a), v, n) # ---------- exceptions / interrupts ---------- def exception(self, code, vaddr=None, refill=False): st = self.cop0.get(12, 0) cause = self.cop0.get(13, 0) & ~0x8000007C cause |= (code & 31) << 2 if not (st & 2): # EXL clear: set EPC # exc_pc/exc_bd are latched at instruction fetch; self.pc may # already point past the faulting instruction by the time a # data access raises. epc = getattr(self, "exc_pc", self.pc) if getattr(self, "exc_bd", False): epc = (epc - 4) & M64 cause |= 0x80000000 # BD self.cop0[14] = epc self.cop0[12] = st | 2 # EXL vec = 0x80000000 if refill else 0x80000180 else: vec = 0x80000180 self.cop0[13] = cause if vaddr is not None: self.cop0[8] = vaddr & M32 # BadVAddr self.cop0[10] = (self.cop0.get(10, 0) & 0x1FFF) | (vaddr & 0xFFFFE000) self.pc = vec self.next_pc = (vec + 4) & M64 def check_interrupts(self): st = self.cop0[12] if (st & 7) != 1: # IE set, EXL/ERL clear return False cause = self.cop0[13] # IP2-7 are LIVE lines (not latched); IP0/1 are software bits ip = ((cause >> 8) & 0x03) | self.int_pending # COP0 timer: Count == Compare -> IP7 (set when count passes compare) if (ip & ((st >> 8) & 0xFF)): self.cop0[13] = (cause & ~0xFF00) | ((ip & 0xFF) << 8) self.exc_pc = self.pc self.exc_bd = self.next_pc != ((self.pc + 4) & M64) self.exception(0) return True return False def tick_timer(self): cnt = (self.cop0.get(9, 0) + 1) & M32 self.cop0[9] = cnt if cnt == self.cop0.get(11, 0): # Compare self.int_pending |= 0x80 # ---------- TLB ---------- def translate(self, va): # XKPHYS (va[63:62] == 0b10): 64-bit direct-unmapped window the game uses # for cart reads (e.g. 0x90000000_xxxxxxxx with Status.KX). Physical addr # is the low bits; the bus masks to 0x1FFFFFFF. if (va >> 62) == 0b10: return va & 0xFFFFFFFF va &= M32 if 0x80000000 <= va < 0xC0000000: return va # KSEG0/1: direct vpn = va >> 13 for ent in self.tlb: if ent is None: continue hi, mask, lo0, lo1 = ent pm = (mask >> 13) | 0 if (hi >> 13) & ~pm == vpn & ~pm: odd = (va >> 12) & 1 if mask == 0 else (va >> (13 + (mask.bit_length() - 13))) & 1 lo = lo1 if odd else lo0 if not (lo & 2): break # invalid pfn = (lo >> 6) & 0xFFFFF off_bits = 12 if mask == 0 else mask.bit_length() return ((pfn << 12) | (va & ((1 << off_bits) - 1))) | 0x80000000 self.exception(2, vaddr=va, refill=True) # TLBL refill raise TLBMiss() # ---------- one instruction ---------- def step(self): self.instr_count += 1 # inline tick_timer (COP0 Count++/Compare) to save a call per instruction c0 = self.cop0 cnt = (c0[9] + 1) & M32 c0[9] = cnt if cnt == c0[11]: self.int_pending |= 0x80 if self.check_interrupts(): return try: self._step_body() except TLBMiss: pass def _step_body(self): self.exc_pc = self.pc self.exc_bd = self.next_pc != ((self.pc + 4) & M64) # fast instruction fetch: code runs from RDRAM via KSEG0/KSEG1, a direct # unmapped map -- skip translate()/bus.read()/_phys for that common case. pc32 = self.pc & M32 if 0x80000000 <= pc32 < 0xC0000000: p = pc32 & 0x1FFFFFFF if p + 4 <= self._rdram_len: rd = self._rdram instr = (rd[p] << 24) | (rd[p + 1] << 16) | (rd[p + 2] << 8) | rd[p + 3] else: instr = self.mrd(pc32, 4) else: instr = self.mrd(pc32, 4) cur_pc = self.pc self.pc = self.next_pc self.next_pc = (self.pc + 4) & M64 if instr == 0: return # NOP fast path op = instr >> 26 rs, rt = (instr >> 21) & 31, (instr >> 16) & 31 rd, sa = (instr >> 11) & 31, (instr >> 6) & 31 imm = instr & 0xFFFF simm = sx16(imm) cls = OPC_CLASSES[self.u.opc(op)] R = self.r; u = self.u; alu = self.alu if cls == "SPECIAL": f = FUNCT_CLASSES[self.u.funct(instr & 0x3F)] self.special(f, instr, rs, rt, rd, sa, cur_pc) elif cls == "REGIMM": g = REGIMM_CLASSES[self.u.regimm(rt)] cond = {"BLTZ": R[rs] >> 63, "BGEZ": 1 - (R[rs] >> 63), "BLTZL": R[rs] >> 63, "BGEZL": 1 - (R[rs] >> 63), "BLTZAL": R[rs] >> 63, "BGEZAL": 1 - (R[rs] >> 63)}.get(g) if cond is None: raise CPUError(f"regimm {rt:#x}") if g.endswith("AL"): self.set_r(31, (cur_pc + 8) & M64) if cond: self.next_pc = (cur_pc + 4 + (simm << 2)) & M64 elif g.endswith("L") and not g.endswith("AL"): # likely: nullify slot self.pc = self.next_pc self.next_pc = (self.pc + 4) & M64 elif cls == "J" or cls == "JAL": if cls == "JAL": self.set_r(31, (cur_pc + 8) & M64) self.next_pc = ((cur_pc + 4) & ~0x0FFFFFFF) | ((instr & 0x3FFFFFF) << 2) elif cls in ("BEQ", "BNE", "BLEZ", "BGTZ", "BEQL", "BNEL", "BLEZL", "BGTZL"): a, b = R[rs], R[rt] taken = {"BEQ": a == b, "BNE": a != b, "BLEZ": (a >> 63) or a == 0, "BGTZ": not ((a >> 63) or a == 0), "BEQL": a == b, "BNEL": a != b, "BLEZL": (a >> 63) or a == 0, "BGTZL": not ((a >> 63) or a == 0)}[cls] if taken: self.next_pc = (cur_pc + 4 + (simm << 2)) & M64 elif cls.endswith("L"): self.pc = self.next_pc self.next_pc = (self.pc + 4) & M64 elif cls in ("ADDI", "ADDIU"): res, _ = alu.add(R[rs] & M32, simm & M32, 32) self.set_r(rt, sx32(res)) elif cls in ("DADDI", "DADDIU"): res, _ = alu.add(R[rs], simm & M64, 64) self.set_r(rt, res) elif cls == "SLTI": self.set_r(rt, self.slt_signed(R[rs], simm & M64)) elif cls == "SLTIU": self.set_r(rt, self.slt_unsigned(R[rs], simm & M64)) elif cls == "ANDI": self.set_r(rt, alu.logic("AND8", R[rs], imm, 64)) elif cls == "ORI": self.set_r(rt, alu.logic("OR8", R[rs], imm, 64)) elif cls == "XORI": self.set_r(rt, alu.logic("XOR8", R[rs], imm, 64)) elif cls == "LUI": self.set_r(rt, sx32(imm << 16)) elif cls == "COP0": self.cop0_op(instr, rs, rt, rd) elif cls == "COP1": self.cop1_op(instr, rs, rt, rd, cur_pc) elif cls in ("CACHE", "LL", "SC", "LLD", "SCD"): if cls == "CACHE": # cache op is in the rt field. Only 0x0D (Create Dirty Exclusive, # D-cache) allocates a line without a memory fetch -- its contents # are architecturally undefined, so HLE it as zeroing the 32-byte # line (some code memclears this way). Every other op (incl. 0x15 # Hit Writeback Invalidate, which previously zeroed RAM and erased # the IPL3 stub) is a no-op for us. if rt == 0x0D: line = ((R[rs] + simm) & M32) & ~0x1F for off in range(0, 32, 4): self.mwr((line + off) & M32, 0, 4) elif cls == "LL": a = (R[rs] + simm) & M64 self.set_r(rt, sx32(self.mrd(a, 4))); self.ll_bit = 1 elif cls == "SC": a = (R[rs] + simm) & M64 if self.ll_bit: self.mwr(a, R[rt] & M32, 4) self.set_r(rt, self.ll_bit); self.ll_bit = 0 elif cls == "LLD": # 64-bit load-linked a = (R[rs] + simm) & M64 self.set_r(rt, self.mrd(a, 8)); self.ll_bit = 1 elif cls == "SCD": # 64-bit store-cond. a = (R[rs] + simm) & M64 if self.ll_bit: self.mwr(a, R[rt], 8) self.set_r(rt, self.ll_bit); self.ll_bit = 0 elif cls in ("LB", "LBU", "LH", "LHU", "LW", "LWU", "LD", "SB", "SH", "SW", "SD"): a = (R[rs] + simm) & M64 if cls == "LB": self.set_r(rt, sx8(self.mrd(a, 1)) & M64) elif cls == "LBU": self.set_r(rt, self.mrd(a, 1)) elif cls == "LH": self.set_r(rt, sx16(self.mrd(a, 2)) & M64) elif cls == "LHU": self.set_r(rt, self.mrd(a, 2)) elif cls == "LW": self.set_r(rt, sx32(self.mrd(a, 4))) elif cls == "LWU": self.set_r(rt, self.mrd(a, 4)) elif cls == "LD": self.set_r(rt, self.mrd(a, 8)) elif cls == "SB": self.mwr(a, R[rt] & 0xFF, 1) elif cls == "SH": self.mwr(a, R[rt] & 0xFFFF, 2) elif cls == "SW": self.mwr(a, R[rt] & M32, 4) else: self.mwr(a, R[rt], 8) elif cls in ("LWL", "LWR", "SWL", "SWR"): a = (R[rs] + simm) & M64 al = a & ~3; off = a & 3 word = self.mrd(al, 4) if cls == "LWL": v = ((word << (8 * off)) & M32) keep = (1 << (8 * off)) - 1 if off else 0 self.set_r(rt, sx32((R[rt] & keep) | v)) elif cls == "LWR": v = word >> (8 * (3 - off)) keep = (M32 << (8 * (off + 1))) & M32 if off != 3 else 0 self.set_r(rt, sx32(((R[rt] & M32) & keep) | v)) elif cls == "SWL": v = (R[rt] & M32) >> (8 * off) keep = ~((M32 >> (8 * off))) & M32 self.mwr(al, (word & keep) | v, 4) else: # SWR v = ((R[rt] & M32) << (8 * (3 - off))) & M32 keep = (1 << (8 * (3 - off))) - 1 self.mwr(al, (word & keep) | v, 4) elif cls in ("LDL", "LDR", "SDL", "SDR"): a = (R[rs] + simm) & M64 al = a & ~7; off = a & 7 dw = self.mrd(al, 8) if cls == "LDL": v = (dw << (8 * off)) & M64 keep = (1 << (8 * off)) - 1 if off else 0 self.set_r(rt, (R[rt] & keep) | v) elif cls == "LDR": v = dw >> (8 * (7 - off)) keep = (M64 << (8 * (off + 1))) & M64 if off != 7 else 0 self.set_r(rt, (R[rt] & keep) | v) elif cls == "SDL": v = R[rt] >> (8 * off) keep = ~(M64 >> (8 * off)) & M64 self.mwr(al, (dw & keep) | v, 8) else: v = (R[rt] << (8 * (7 - off))) & M64 keep = (1 << (8 * (7 - off))) - 1 self.mwr(al, (dw & keep) | v, 8) elif cls in ("LWC1", "SWC1", "LDC1", "SDC1"): a = (R[rs] + simm) & M64 if cls == "LWC1": self.set_fpr32(rt, self.mrd(a, 4)) elif cls == "LDC1": self.set_fpr64(rt, self.mrd(a, 8)) elif cls == "SWC1": self.mwr(a, self.get_fpr32(rt), 4) else: self.mwr(a, self.get_fpr64(rt), 8) else: raise CPUError(f"opcode {op:#04x} ({cls}) at {cur_pc:#x}") def special(self, f, instr, rs, rt, rd, sa, cur_pc): R = self.r; alu = self.alu if f == "SLL": self.set_r(rd, sx32(alu.shl(R[rt] & M32, sa, 32))) elif f == "SRL": self.set_r(rd, sx32(alu.shr(R[rt] & M32, sa, 32))) elif f == "SRA": self.set_r(rd, sx32(alu.shr(R[rt] & M32, sa, 32, arith=True))) elif f == "SLLV": self.set_r(rd, sx32(alu.shl(R[rt] & M32, R[rs] & 31, 32))) elif f == "SRLV": self.set_r(rd, sx32(alu.shr(R[rt] & M32, R[rs] & 31, 32))) elif f == "SRAV": self.set_r(rd, sx32(alu.shr(R[rt] & M32, R[rs] & 31, 32, arith=True))) elif f == "DSLL": self.set_r(rd, alu.shl(R[rt], sa, 64)) elif f == "DSRL": self.set_r(rd, alu.shr(R[rt], sa, 64)) elif f == "DSRA": self.set_r(rd, alu.shr(R[rt], sa, 64, arith=True)) elif f == "DSLL32": self.set_r(rd, alu.shl(R[rt], sa + 32, 64)) elif f == "DSRL32": self.set_r(rd, alu.shr(R[rt], sa + 32, 64)) elif f == "DSRA32": self.set_r(rd, alu.shr(R[rt], sa + 32, 64, arith=True)) elif f == "DSLLV": self.set_r(rd, alu.shl(R[rt], R[rs] & 63, 64)) elif f == "DSRLV": self.set_r(rd, alu.shr(R[rt], R[rs] & 63, 64)) elif f == "DSRAV": self.set_r(rd, alu.shr(R[rt], R[rs] & 63, 64, arith=True)) elif f == "JR": self.next_pc = R[rs] elif f == "JALR": self.set_r(rd, (cur_pc + 8) & M64); self.next_pc = R[rs] elif f in ("ADD", "ADDU"): res, _ = alu.add(R[rs] & M32, R[rt] & M32, 32) self.set_r(rd, sx32(res)) elif f in ("SUB", "SUBU"): res, _ = alu.sub(R[rs] & M32, R[rt] & M32, 32) self.set_r(rd, sx32(res)) elif f in ("DADD", "DADDU"): res, _ = alu.add(R[rs], R[rt], 64); self.set_r(rd, res) elif f in ("DSUB", "DSUBU"): res, _ = alu.sub(R[rs], R[rt], 64); self.set_r(rd, res) elif f == "AND": self.set_r(rd, alu.logic("AND8", R[rs], R[rt], 64)) elif f == "OR": self.set_r(rd, alu.logic("OR8", R[rs], R[rt], 64)) elif f == "XOR": self.set_r(rd, alu.logic("XOR8", R[rs], R[rt], 64)) elif f == "NOR": self.set_r(rd, alu.logic("NOR8", R[rs], R[rt], 64)) elif f == "SLT": self.set_r(rd, self.slt_signed(R[rs], R[rt])) elif f == "SLTU": self.set_r(rd, self.slt_unsigned(R[rs], R[rt])) elif f == "MFHI": self.set_r(rd, self.hi) elif f == "MFLO": self.set_r(rd, self.lo) elif f == "MTHI": self.hi = R[rs] elif f == "MTLO": self.lo = R[rs] elif f in ("MULT", "MULTU"): a, b = R[rs] & M32, R[rt] & M32 if f == "MULT": sa_, sb_ = sx32i(a), sx32i(b) full = self.alu.mul(abs(sa_), abs(sb_), 32) if (sa_ < 0) != (sb_ < 0): full = (-full) & M64 else: full = self.alu.mul(a, b, 32) self.lo = sx32(full & M32); self.hi = sx32(full >> 32) elif f in ("DMULT", "DMULTU"): a, b = R[rs], R[rt] if f == "DMULT": sa_ = self.sgn64(a); sb_ = self.sgn64(b) full = self.alu.mul(abs(sa_), abs(sb_), 64) if (sa_ < 0) != (sb_ < 0): full = (-full) & ((1 << 128) - 1) else: full = self.alu.mul(a, b, 64) self.lo = full & M64; self.hi = (full >> 64) & M64 elif f in ("DIV", "DIVU"): a, b = R[rs] & M32, R[rt] & M32 if f == "DIV": sa_, sb_ = sx32i(a), sx32i(b) if sb_ == 0: self.lo = self.r[rs]; self.hi = 0 else: q0, r0 = self.alu.divmod_(abs(sa_), abs(sb_), 32) q = -q0 if (sa_ < 0) != (sb_ < 0) else q0 r2 = -r0 if sa_ < 0 else r0 self.lo = sx32(q & M32); self.hi = sx32(r2 & M32) else: if b == 0: self.lo = self.r[rs]; self.hi = 0 else: q, r2 = self.alu.divmod_(a, b, 32) self.lo = sx32(q); self.hi = sx32(r2) elif f in ("DDIV", "DDIVU"): a, b = R[rs], R[rt] if f == "DDIV": sa_, sb_ = self.sgn64(a), self.sgn64(b) if sb_ == 0: self.lo = a; self.hi = 0 else: q0, r0 = self.alu.divmod_(abs(sa_), abs(sb_), 64) q = -q0 if (sa_ < 0) != (sb_ < 0) else q0 r2 = -r0 if sa_ < 0 else r0 self.lo = q & M64; self.hi = r2 & M64 else: if b == 0: self.lo = a; self.hi = 0 else: q, r2 = self.alu.divmod_(a, b, 64) self.lo = q; self.hi = r2 elif f == "SYNC": pass elif f == "TEQ": if R[rs] == R[rt]: self.exception(13) # Trap elif f == "SYSCALL": self.exception(8) elif f == "BREAK": self.exception(9) else: raise CPUError(f"funct {instr & 0x3F:#04x} ({f}) at {cur_pc:#x}") # ---------- COP1 register file (FR=0 pairs / FR=1 flat) ---------- def _fr(self): return (self.cop0.get(12, 0) >> 26) & 1 def get_fpr32(self, i): if self._fr(): return self.fpr[i] & M32 base = self.fpr[i & ~1] return (base >> 32) & M32 if i & 1 else base & M32 def set_fpr32(self, i, v): v &= M32 if self._fr(): self.fpr[i] = (self.fpr[i] & 0xFFFFFFFF00000000) | v else: j = i & ~1 if i & 1: self.fpr[j] = (self.fpr[j] & M32) | (v << 32) else: self.fpr[j] = (self.fpr[j] & 0xFFFFFFFF00000000) | v def get_fpr64(self, i): return self.fpr[i if self._fr() else i & ~1] def set_fpr64(self, i, v): self.fpr[i if self._fr() else i & ~1] = v & M64 def cop1_op(self, instr, rs, rt, rd, cur_pc): ft, fs, fd = rt, rd, (instr >> 6) & 31 funct = instr & 0x3F fp = self.fp fp.rm = self.fcsr & 3 if rs == 0: # MFC1 self.set_r(rt, sx32(self.get_fpr32(rd))); return if rs == 1: # DMFC1 self.set_r(rt, self.get_fpr64(rd)); return if rs == 4: # MTC1 self.set_fpr32(rd, self.r[rt]); return if rs == 5: # DMTC1 self.set_fpr64(rd, self.r[rt]); return if rs == 2: # CFC1 self.set_r(rt, sx32(self.fcr0 if rd == 0 else self.fcsr)); return if rs == 6: # CTC1 if rd == 31: self.fcsr = self.r[rt] & M32 return if rs == 8: # BC1 d = sx16(instr & 0xFFFF) taken = bool(self.fcsr & (1 << 23)) == bool(rt & 1) if taken: self.next_pc = (cur_pc + 4 + (d << 2)) & M64 elif rt & 2: # likely: nullify slot self.pc = self.next_pc self.next_pc = (self.pc + 4) & M64 return if rs in (16, 17): # S / D arithmetic f = F32 if rs == 16 else F64 get = self.get_fpr32 if rs == 16 else self.get_fpr64 put = self.set_fpr32 if rs == 16 else self.set_fpr64 a = get(fs) if funct in (0, 1, 2, 3): b = get(ft) r = (fp.add(a, b, f) if funct == 0 else fp.add(a, b, f, sub=True) if funct == 1 else fp.mul(a, b, f) if funct == 2 else fp.div(a, b, f)) put(fd, r); return if funct == 4: put(fd, fp.sqrt(a, f)); return if funct == 5: put(fd, a & ~(1 << (f["W"] - 1))); return # ABS if funct == 6: put(fd, a); return # MOV if funct == 7: put(fd, a ^ (1 << (f["W"] - 1))); return # NEG if 8 <= funct <= 15: # ROUND/TRUNC/CEIL/FLOOR width = 64 if funct < 12 else 32 rm = (funct - 8) & 3 v = fp.f2i(a, f, width, rm) (self.set_fpr64 if width == 64 else self.set_fpr32)(fd, v) return if funct == 32: # CVT.S.D self.set_fpr32(fd, fp.cvt_f2f(a, f, F32)); return if funct == 33: # CVT.D.S self.set_fpr64(fd, fp.cvt_f2f(a, f, F64)); return if funct == 36: self.set_fpr32(fd, fp.f2i(a, f, 32, self.fcsr & 3)); return if funct == 37: self.set_fpr64(fd, fp.f2i(a, f, 64, self.fcsr & 3)); return if funct >= 0x30: # C.cond lt, eq, un = fp.cmp(a, get(ft), f) cond = funct & 0xF c = ((cond >> 2) & 1 & lt) | ((cond >> 1) & 1 & eq) | (cond & 1 & un) self.fcsr = (self.fcsr & ~(1 << 23)) | (c << 23) return raise CPUError(f"cop1 {('S','D')[rs-16]} funct {funct:#x}") if rs in (20, 21): # W / L source: CVT only width = 32 if rs == 20 else 64 v = self.get_fpr32(fs) if rs == 20 else self.get_fpr64(fs) if funct == 32: self.set_fpr32(fd, fp.i2f(v, width, F32)); return if funct == 33: self.set_fpr64(fd, fp.i2f(v, width, F64)); return raise CPUError(f"cop1 W/L funct {funct:#x}") raise CPUError(f"cop1 rs={rs}") def cop0_op(self, instr, rs, rt, rd): if rs == 0: # MFC0 v = self.cop0.get(rd, 0) & M32 if rd == 13: v = (v & ~0xFC00) | ((self.int_pending & 0xFC) << 8) self.set_r(rt, sx32(v)) elif rs == 4: # MTC0 self.cop0[rd] = self.r[rt] & M32 if rd == 11: # Compare write: ack timer self.int_pending &= ~0x80 elif rs == 1: # DMFC0 self.set_r(rt, self.cop0.get(rd, 0)) elif rs == 5: # DMTC0 self.cop0[rd] = self.r[rt] elif rs & 0x10: f = instr & 0x3F if f == 0x18: # ERET self.pc = self.cop0.get(14, 0) & M64 self.next_pc = (self.pc + 4) & M64 self.cop0[12] = self.cop0.get(12, 0) & ~2 # clear EXL elif f == 0x02: # TLBWI i = self.cop0.get(0, 0) & 31 self.tlb[i] = (self.cop0.get(10, 0), self.cop0.get(5, 0), self.cop0.get(2, 0), self.cop0.get(3, 0)) elif f == 0x06: # TLBWR i = (self.instr_count % 24) + 8 self.tlb[i & 31] = (self.cop0.get(10, 0), self.cop0.get(5, 0), self.cop0.get(2, 0), self.cop0.get(3, 0)) elif f == 0x08: # TLBP self.cop0[0] = 0x80000000 vpn = self.cop0.get(10, 0) >> 13 for i, ent in enumerate(self.tlb): if ent and (ent[0] >> 13) == vpn: self.cop0[0] = i break elif f == 0x01: # TLBR ent = self.tlb[self.cop0.get(0, 0) & 31] if ent: (self.cop0[10], self.cop0[5], self.cop0[2], self.cop0[3]) = ent else: raise CPUError(f"cop0 funct {f:#x}") else: raise CPUError(f"cop0 rs={rs}")