neural-n64 / mips_core.py
Quazim0t0's picture
N64 engine: 2D SP-DMA fix, libdragon boot, 7 CPU/RSP fixes, SDR fix, CPU fast paths; C cores rebuilt. Portal intact.
c167138 verified
Raw
History Blame Contribute Delete
27.5 kB
"""
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}")