// SPDX-License-Identifier: MIT // Copyright (c) 2026 Raphael Amorim // it covers the primary integer ISA: shifts (SLL/SRL/SRA/SLLV/SRLV/SRAV), // jumps and branches (J/JAL/JR/JALR/BEQ/BNE/BLEZ/BGTZ/BLTZ/BGEZ/BLTZAL/ // BGEZAL), ALU R-type (ADD/ADDU/SUB/SUBU/AND/OR/XOR/NOR/SLT/SLTU), // ALU I-type (ADDI/ADDIU/ANDI/ORI/XORI/SLTI/SLTIU/LUI), MULT/MULTU/ // DIV/DIVU, MFHI/MTHI/MFLO/MTLO, loads (LB/LBU/LH/LHU/LW/LWL/LWR), // stores (SB/SH/SW/SWL/SWR), COP0 MFC0/MTC0/RFE, SYSCALL/BREAK. // // stubbed: // - COP2 raises coprocessor-unusable on every access const { busRead32, busRead16, busRead8, busWrite32, busWrite16, busWrite8 } = import("bus"); const { Gpu } = import("gpu"); const { createDma } = import("dma"); const { createIrq } = import("irq"); const { Bus } = import("bus"); const { Timer } = import("timer"); const { createGte, gteDataRead, gteDataWrite, gteCtrlRead, gteCtrlWrite, gteExec, gteOpCycles } = import("gte"); const { Cdrom } = import("cdrom"); const { Mcd } = import("mcd"); const { Sio1 } = import("sio1"); const { Mdec } = import("mdec"); const { Disc } = import("disc"); const { Vec } = import("std/collections"); const { print } = import("std/fmt"); const Cpu = struct { pc: u32, nextPc: u32, savedPc: u32, hi: u32, lo: u32, loadD: u32, loadV: u32, branch: u8, delaySlot: u8, branchTaken: u8, halted: u8, cycles: u64, }; const C0_BPC: u32 = 3; const C0_BDA: u32 = 5; const C0_JUMPDEST: u32 = 6; const C0_DCIC: u32 = 7; const C0_BADVADDR: u32 = 8; const C0_BDAM: u32 = 9; const C0_BPCM: u32 = 11; const C0_SR: u32 = 12; const C0_CAUSE: u32 = 13; const C0_EPC: u32 = 14; const C0_PRID: u32 = 15; const SR_IEC: u32 = 0x00000001; const SR_ISC: u32 = 0x00010000; const SR_BEV: u32 = 0x00400000; const CAUSE_INT: u32 = 0x00; const CAUSE_IBE: u32 = 0x18; const CAUSE_DBE: u32 = 0x1C; const CAUSE_ADEL: u32 = 0x10; const CAUSE_ADES: u32 = 0x14; const CAUSE_SYSCALL: u32 = 0x20; const CAUSE_BP: u32 = 0x24; const CAUSE_RI: u32 = 0x28; const CAUSE_CPU: u32 = 0x2C; const CAUSE_OV: u32 = 0x30; pub fn createRegFile(n: u32) Vec(u32) { return Vec(u32).filled(0, n); } pub fn gprRead(regs: *mut[] u32, idx: u32) u32 { return regs[idx]; } pub fn gprWrite(regs: *mut[] u32, idx: u32, val: u32) { if (idx == 0) { return; } regs[idx] = val; } pub fn cop0Read(cop0: *mut[] u32, idx: u32) u32 { return cop0[idx]; } pub fn cop0Write(cop0: *mut[] u32, idx: u32, val: u32) { cop0[idx] = val; } pub fn freshCpu() Cpu { var c: Cpu = Cpu { pc: 0xBFC00000, nextPc: 0xBFC00004, savedPc: 0, hi: 0, lo: 0, loadD: 0, loadV: 0, branch: 0, delaySlot: 0, branchTaken: 0, halted: 0, cycles: 0, }; return c; } pub fn freshCpuAt(pc: u32) Cpu { var c: Cpu = freshCpu(); c.pc = pc; c.nextPc = pc + 4; return c; } pub fn signExt16(v: u32) u32 { if ((v & 0x8000) != 0) { return v | 0xFFFF0000; } return v & 0x0000FFFF; } pub fn signExt8(v: u32) u32 { if ((v & 0x80) != 0) { return v | 0xFFFFFF00; } return v & 0x000000FF; } pub fn lessThanSigned(a: u32, b: u32) bool { const sa: u32 = a & 0x80000000; const sb: u32 = b & 0x80000000; if (sa != sb) { return sa != 0; } return a < b; } pub fn signedLtZero(a: u32) bool { return (a & 0x80000000) != 0; } pub fn signedGtZero(a: u32) bool { return a != 0 && (a & 0x80000000) == 0; } pub fn signedLeZero(a: u32) bool { return a == 0 || (a & 0x80000000) != 0; } pub fn signedGeZero(a: u32) bool { return (a & 0x80000000) == 0; } pub fn divU32(a: u32, b: u32) u32 { if (b == 0) { return 0xFFFFFFFF; } return a / b; } pub fn modU32(a: u32, b: u32) u32 { if (b == 0) { return a; } return a % b; } pub fn absI32(a: u32) u32 { if ((a & 0x80000000) == 0) { return a; } return (~a) + 1; } pub fn divI32(a: u32, b: u32) u32 { if (b == 0) { if ((a & 0x80000000) != 0) { return 1; } return 0xFFFFFFFF; } if (a == 0x80000000 && b == 0xFFFFFFFF) { return 0x80000000; } return ((a as i32) / (b as i32)) as u32; } pub fn modI32(a: u32, b: u32) u32 { if (b == 0) { return a; } if (a == 0x80000000 && b == 0xFFFFFFFF) { return 0; } return ((a as i32) % (b as i32)) as u32; } pub fn raiseException(c: mut Cpu, cop0: *mut[] u32, cause: u32) { var sr: u32 = cop0Read(cop0, C0_SR); var causeReg: u32 = cop0Read(cop0, C0_CAUSE); causeReg = (causeReg & 0xFFFFFF80) | (cause & 0x7C); var epc: u32 = c.savedPc; if (c.delaySlot != 0) { epc = epc - 4; causeReg = causeReg | 0x80000000; } cop0Write(cop0, C0_EPC, epc); cop0Write(cop0, C0_CAUSE, causeReg); const mode: u32 = sr & 0x3F; sr = (sr & 0xFFFFFFC0) | ((mode << 2) & 0x3F); cop0Write(cop0, C0_SR, sr); if ((sr & SR_BEV) != 0) { c.pc = 0xBFC00180; } else { c.pc = 0x80000080; } c.nextPc = c.pc + 4; } pub fn cop0Rfe(cop0: *mut[] u32) { var sr: u32 = cop0Read(cop0, C0_SR); const mode: u32 = sr & 0x3F; sr = (sr & 0xFFFFFFF0) | (mode >> 2); cop0Write(cop0, C0_SR, sr); } pub fn opPrim(opc: u32) u32 { return (opc >> 26) & 0x3F; } pub fn opRs (opc: u32) u32 { return (opc >> 21) & 0x1F; } pub fn opRt (opc: u32) u32 { return (opc >> 16) & 0x1F; } pub fn opRd (opc: u32) u32 { return (opc >> 11) & 0x1F; } pub fn opSa (opc: u32) u32 { return (opc >> 6) & 0x1F; } pub fn opFn (opc: u32) u32 { return opc & 0x3F; } pub fn opImm (opc: u32) u32 { return opc & 0xFFFF; } pub fn opImmS(opc: u32) u32 { return signExt16(opc & 0xFFFF); } pub fn opTgt (opc: u32) u32 { return opc & 0x03FFFFFF; } pub fn doBranch(c: mut Cpu, offsetShifted: u32) { c.nextPc = (c.nextPc + offsetShifted) - 4; c.branch = 1; c.branchTaken = 1; } pub fn commitLoad(c: mut Cpu, regs: *mut[] u32) { if (c.loadD != 0) { gprWrite(regs, c.loadD, c.loadV); } c.loadD = 0; c.loadV = 0; } pub fn iLui(c: mut Cpu, opc: u32, regs: *mut[] u32) { commitLoad(c, regs); gprWrite(regs, opRt(opc), opImm(opc) << 16); } pub fn iOri(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); commitLoad(c, regs); gprWrite(regs, opRt(opc), s | opImm(opc)); } pub fn iAndi(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); commitLoad(c, regs); gprWrite(regs, opRt(opc), s & opImm(opc)); } pub fn iXori(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); commitLoad(c, regs); gprWrite(regs, opRt(opc), s ^ opImm(opc)); } pub fn iAddiu(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); commitLoad(c, regs); gprWrite(regs, opRt(opc), s + opImmS(opc)); } pub fn iAddi(c: mut Cpu, opc: u32, regs: *mut[] u32, cop0: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const imm: u32 = opImmS(opc); commitLoad(c, regs); const r: u32 = s + imm; const ovf: u32 = (s ^ r) & (imm ^ r); if ((ovf & 0x80000000) != 0) { raiseException(c, cop0, CAUSE_OV); return; } gprWrite(regs, opRt(opc), r); } pub fn iSlti(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); commitLoad(c, regs); var v: u32 = 0; if (lessThanSigned(s, opImmS(opc))) { v = 1; } gprWrite(regs, opRt(opc), v); } pub fn iSltiu(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); commitLoad(c, regs); var v: u32 = 0; if (s < opImmS(opc)) { v = 1; } gprWrite(regs, opRt(opc), v); } pub fn iJ(c: mut Cpu, opc: u32, regs: *mut[] u32) { commitLoad(c, regs); c.nextPc = (c.nextPc & 0xF0000000) | (opTgt(opc) << 2); c.branch = 1; c.branchTaken = 1; } pub fn iJal(c: mut Cpu, opc: u32, regs: *mut[] u32) { const ret: u32 = c.nextPc; commitLoad(c, regs); gprWrite(regs, 31, ret); c.nextPc = (c.nextPc & 0xF0000000) | (opTgt(opc) << 2); c.branch = 1; c.branchTaken = 1; } pub fn iBeq(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); c.branch = 1; if (s == t) { doBranch(c, opImmS(opc) << 2); } } pub fn iBne(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); c.branch = 1; if (s != t) { doBranch(c, opImmS(opc) << 2); } } pub fn iBlez(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); commitLoad(c, regs); c.branch = 1; if (signedLeZero(s)) { doBranch(c, opImmS(opc) << 2); } } pub fn iBgtz(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); commitLoad(c, regs); c.branch = 1; if (signedGtZero(s)) { doBranch(c, opImmS(opc) << 2); } } pub fn iRegimm(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const variant: u32 = opRt(opc); commitLoad(c, regs); c.branch = 1; if (variant == 0x10 || variant == 0x11) { gprWrite(regs, 31, c.nextPc); } var take: bool = false; if ((variant & 1) == 0) { take = signedLtZero(s); } else { take = signedGeZero(s); } if (take) { doBranch(c, opImmS(opc) << 2); } } pub fn iLb(c: mut Cpu, opc: u32, bus: Bus, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const addr: u32 = s + opImmS(opc); if (c.loadD != opRt(opc)) { commitLoad(c, regs); } c.loadD = opRt(opc); c.loadV = signExt8(busRead8(bus, addr)); } pub fn iLbu(c: mut Cpu, opc: u32, bus: Bus, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const addr: u32 = s + opImmS(opc); if (c.loadD != opRt(opc)) { commitLoad(c, regs); } c.loadD = opRt(opc); c.loadV = busRead8(bus, addr) & 0xFF; } pub fn iLh(c: mut Cpu, opc: u32, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const addr: u32 = s + opImmS(opc); if (c.loadD != opRt(opc)) { commitLoad(c, regs); } if ((addr & 1) != 0) { cop0Write(cop0, C0_BADVADDR, addr); raiseException(c, cop0, CAUSE_ADEL); return; } c.loadD = opRt(opc); c.loadV = signExt16(busRead16(bus, addr)); } pub fn iLhu(c: mut Cpu, opc: u32, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const addr: u32 = s + opImmS(opc); if (c.loadD != opRt(opc)) { commitLoad(c, regs); } if ((addr & 1) != 0) { cop0Write(cop0, C0_BADVADDR, addr); raiseException(c, cop0, CAUSE_ADEL); return; } c.loadD = opRt(opc); c.loadV = busRead16(bus, addr) & 0xFFFF; } pub fn iLw(c: mut Cpu, opc: u32, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const addr: u32 = s + opImmS(opc); if (c.loadD != opRt(opc)) { commitLoad(c, regs); } if ((addr & 3) != 0) { cop0Write(cop0, C0_BADVADDR, addr); raiseException(c, cop0, CAUSE_ADEL); return; } c.loadD = opRt(opc); c.loadV = busRead32(bus, addr); } pub fn iLwl(c: mut Cpu, opc: u32, bus: Bus, regs: *mut[] u32) { const rt: u32 = opRt(opc); const s: u32 = gprRead(regs, opRs(opc)); var t: u32 = gprRead(regs, rt); const addr: u32 = s + opImmS(opc); const aligned: u32 = busRead32(bus, addr & 0xFFFFFFFC); if (c.loadD == rt) { t = c.loadV; } else { commitLoad(c, regs); } const shift: u32 = (addr & 3) * 8; const mask: u32 = 0x00FFFFFF >> shift; const value: u32 = (t & mask) | (aligned << (24 - shift)); c.loadD = rt; c.loadV = value; } pub fn iLwr(c: mut Cpu, opc: u32, bus: Bus, regs: *mut[] u32) { const rt: u32 = opRt(opc); const s: u32 = gprRead(regs, opRs(opc)); var t: u32 = gprRead(regs, rt); const addr: u32 = s + opImmS(opc); const aligned: u32 = busRead32(bus, addr & 0xFFFFFFFC); if (c.loadD == rt) { t = c.loadV; } else { commitLoad(c, regs); } const shift: u32 = (addr & 3) * 8; const mask: u32 = 0xFFFFFF00 << (24 - shift); const value: u32 = (t & mask) | (aligned >> shift); c.loadD = rt; c.loadV = value; } pub fn iSb(c: mut Cpu, opc: u32, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); if ((cop0Read(cop0, C0_SR) & SR_ISC) != 0) { return; } busWrite8(bus, cop0, s + opImmS(opc), t & 0xFF); } pub fn iSh(c: mut Cpu, opc: u32, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); const addr: u32 = s + opImmS(opc); commitLoad(c, regs); if ((cop0Read(cop0, C0_SR) & SR_ISC) != 0) { return; } if ((addr & 1) != 0) { cop0Write(cop0, C0_BADVADDR, addr); raiseException(c, cop0, CAUSE_ADES); return; } busWrite16(bus, cop0, addr, t & 0xFFFF); } pub fn iSw(c: mut Cpu, opc: u32, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); const addr: u32 = s + opImmS(opc); commitLoad(c, regs); if ((cop0Read(cop0, C0_SR) & SR_ISC) != 0) { return; } if ((addr & 3) != 0) { cop0Write(cop0, C0_BADVADDR, addr); raiseException(c, cop0, CAUSE_ADES); return; } busWrite32(bus, cop0, addr, t); } pub fn iSwl(c: mut Cpu, opc: u32, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); const addr: u32 = s + opImmS(opc); const aligned: u32 = addr & 0xFFFFFFFC; const v0: u32 = busRead32(bus, aligned); var v: u32 = v0; const sel: u32 = addr & 3; if (sel == 0) { v = (v0 & 0xFFFFFF00) | (t >> 24); } if (sel == 1) { v = (v0 & 0xFFFF0000) | (t >> 16); } if (sel == 2) { v = (v0 & 0xFF000000) | (t >> 8); } if (sel == 3) { v = t; } busWrite32(bus, cop0, aligned, v); } pub fn iSwr(c: mut Cpu, opc: u32, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); const addr: u32 = s + opImmS(opc); const aligned: u32 = addr & 0xFFFFFFFC; const v0: u32 = busRead32(bus, aligned); var v: u32 = v0; const sel: u32 = addr & 3; if (sel == 0) { v = t; } if (sel == 1) { v = (v0 & 0x000000FF) | (t << 8); } if (sel == 2) { v = (v0 & 0x0000FFFF) | (t << 16); } if (sel == 3) { v = (v0 & 0x00FFFFFF) | (t << 24); } busWrite32(bus, cop0, aligned, v); } pub fn iSll(c: mut Cpu, opc: u32, regs: *mut[] u32) { const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); gprWrite(regs, opRd(opc), t << opSa(opc)); } pub fn iSrl(c: mut Cpu, opc: u32, regs: *mut[] u32) { const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); gprWrite(regs, opRd(opc), t >> opSa(opc)); } pub fn iSra(c: mut Cpu, opc: u32, regs: *mut[] u32) { const t: u32 = gprRead(regs, opRt(opc)); const sa: u32 = opSa(opc); commitLoad(c, regs); var v: u32 = t >> sa; if ((t & 0x80000000) != 0 && sa != 0) { const fill: u32 = (0xFFFFFFFF << (32 - sa)) & 0xFFFFFFFF; v = v | fill; } gprWrite(regs, opRd(opc), v); } pub fn iSllv(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); gprWrite(regs, opRd(opc), t << (s & 0x1F)); } pub fn iSrlv(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); gprWrite(regs, opRd(opc), t >> (s & 0x1F)); } pub fn iSrav(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); const sa: u32 = s & 0x1F; commitLoad(c, regs); var v: u32 = t >> sa; if ((t & 0x80000000) != 0 && sa != 0) { const fill: u32 = (0xFFFFFFFF << (32 - sa)) & 0xFFFFFFFF; v = v | fill; } gprWrite(regs, opRd(opc), v); } pub fn iJr(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); commitLoad(c, regs); c.nextPc = s; c.branch = 1; c.branchTaken = 1; } pub fn iJalr(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const ret: u32 = c.nextPc; commitLoad(c, regs); gprWrite(regs, opRd(opc), ret); c.nextPc = s; c.branch = 1; c.branchTaken = 1; } pub fn iSyscall(c: mut Cpu, regs: *mut[] u32, cop0: *mut[] u32) { commitLoad(c, regs); raiseException(c, cop0, CAUSE_SYSCALL); } pub fn iBreak(c: mut Cpu, regs: *mut[] u32, cop0: *mut[] u32) { commitLoad(c, regs); raiseException(c, cop0, CAUSE_BP); } pub fn iMfhi(c: mut Cpu, opc: u32, regs: *mut[] u32) { commitLoad(c, regs); gprWrite(regs, opRd(opc), c.hi); } pub fn iMflo(c: mut Cpu, opc: u32, regs: *mut[] u32) { commitLoad(c, regs); gprWrite(regs, opRd(opc), c.lo); } pub fn iMthi(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); commitLoad(c, regs); c.hi = s; } pub fn iMtlo(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); commitLoad(c, regs); c.lo = s; } pub fn umul64(a: u32, b: u32) u64 { return (a as u64) * (b as u64); } pub fn negU64(v: u64) u64 { return (~v) + 1; } pub fn iMult(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); const neg: bool = ((s ^ t) & 0x80000000) != 0; const au: u32 = absI32(s); const bu: u32 = absI32(t); var w: u64 = umul64(au, bu); if (neg) { w = negU64(w); } c.lo = (w & 0xFFFFFFFF) as u32; c.hi = ((w >> 32) & 0xFFFFFFFF) as u32; } pub fn iMultu(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); const w: u64 = umul64(s, t); c.lo = (w & 0xFFFFFFFF) as u32; c.hi = ((w >> 32) & 0xFFFFFFFF) as u32; } pub fn iDiv(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); if (t == 0) { c.hi = s; if (signedLtZero(s)) { c.lo = 1; } else { c.lo = 0xFFFFFFFF; } return; } if (s == 0x80000000 && t == 0xFFFFFFFF) { c.hi = 0; c.lo = 0x80000000; return; } c.lo = divI32(s, t); c.hi = modI32(s, t); } pub fn iDivu(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); if (t == 0) { c.hi = s; c.lo = 0xFFFFFFFF; return; } c.lo = divU32(s, t); c.hi = modU32(s, t); } pub fn iAdd(c: mut Cpu, opc: u32, regs: *mut[] u32, cop0: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); const r: u32 = s + t; const ovf: u32 = (s ^ r) & (t ^ r); if ((ovf & 0x80000000) != 0) { raiseException(c, cop0, CAUSE_OV); return; } gprWrite(regs, opRd(opc), r); } pub fn iAddu(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); gprWrite(regs, opRd(opc), s + t); } pub fn iSub(c: mut Cpu, opc: u32, regs: *mut[] u32, cop0: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); const r: u32 = s - t; const ovf: u32 = (s ^ t) & (s ^ r); if ((ovf & 0x80000000) != 0) { raiseException(c, cop0, CAUSE_OV); return; } gprWrite(regs, opRd(opc), r); } pub fn iSubu(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); gprWrite(regs, opRd(opc), s - t); } pub fn iAnd(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); gprWrite(regs, opRd(opc), s & t); } pub fn iOr(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); gprWrite(regs, opRd(opc), s | t); } pub fn iXor(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); gprWrite(regs, opRd(opc), s ^ t); } pub fn iNor(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); gprWrite(regs, opRd(opc), ~(s | t)); } pub fn iSlt(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); var v: u32 = 0; if (lessThanSigned(s, t)) { v = 1; } gprWrite(regs, opRd(opc), v); } pub fn iSltu(c: mut Cpu, opc: u32, regs: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); var v: u32 = 0; if (s < t) { v = 1; } gprWrite(regs, opRd(opc), v); } pub fn iMfc0(c: mut Cpu, opc: u32, regs: *mut[] u32, cop0: *mut[] u32) { const v: u32 = cop0Read(cop0, opRd(opc)); commitLoad(c, regs); c.loadD = opRt(opc); c.loadV = v; } pub fn cop0WriteMask(idx: u32) u32 { if (idx == C0_CAUSE) { return 0x00000300; } if (idx == C0_BADVADDR) { return 0; } if (idx == C0_EPC) { return 0; } return 0xFFFFFFFF; } pub fn iMtc0(c: mut Cpu, opc: u32, regs: *mut[] u32, cop0: *mut[] u32) { const t: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); const rd: u32 = opRd(opc); const mask: u32 = cop0WriteMask(rd); const old: u32 = cop0Read(cop0, rd); cop0Write(cop0, rd, (old & (~mask)) | (t & mask)); } pub fn iRfe(c: mut Cpu, regs: *mut[] u32, cop0: *mut[] u32) { commitLoad(c, regs); cop0Rfe(cop0); } pub fn iCop2Unimpl(c: mut Cpu, opc: u32, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { const rs: u32 = opRs(opc); if (rs == 0x00) { commitLoad(c, regs); c.loadD = opRt(opc); c.loadV = gteDataRead(bus.gte.ptr, opRd(opc)); return; } if (rs == 0x02) { commitLoad(c, regs); c.loadD = opRt(opc); c.loadV = gteCtrlRead(bus.gte.ptr, opRd(opc)); return; } if (rs == 0x04) { const v: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); gteDataWrite(bus.gte.ptr, opRd(opc), v); return; } if (rs == 0x06) { const v: u32 = gprRead(regs, opRt(opc)); commitLoad(c, regs); gteCtrlWrite(bus.gte.ptr, opRd(opc), v); return; } commitLoad(c, regs); gteExec(bus.gte.ptr, opc); c.cycles = c.cycles + (gteOpCycles(opc) as u64) - 2; } pub fn iCop2Lwc(c: mut Cpu, opc: u32, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const addr: u32 = s + opImmS(opc); commitLoad(c, regs); if ((addr & 3) != 0) { cop0Write(cop0, C0_BADVADDR, addr); raiseException(c, cop0, CAUSE_ADEL); return; } gteDataWrite(bus.gte.ptr, opRt(opc), busRead32(bus, addr)); } pub fn iCop2Swc(c: mut Cpu, opc: u32, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { const s: u32 = gprRead(regs, opRs(opc)); const addr: u32 = s + opImmS(opc); commitLoad(c, regs); if ((addr & 3) != 0) { cop0Write(cop0, C0_BADVADDR, addr); raiseException(c, cop0, CAUSE_ADES); return; } if ((cop0Read(cop0, C0_SR) & SR_ISC) != 0) { return; } busWrite32(bus, cop0, addr, gteDataRead(bus.gte.ptr, opRt(opc))); } pub fn illegal(c: mut Cpu, opc: u32, regs: *mut[] u32, cop0: *mut[] u32) { commitLoad(c, regs); raiseException(c, cop0, CAUSE_RI); } pub fn dispatchSpecial(c: mut Cpu, opc: u32, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { const f: u32 = opFn(opc); if (f == 0x00) { iSll(c, opc, regs); return; } if (f == 0x02) { iSrl(c, opc, regs); return; } if (f == 0x03) { iSra(c, opc, regs); return; } if (f == 0x04) { iSllv(c, opc, regs); return; } if (f == 0x06) { iSrlv(c, opc, regs); return; } if (f == 0x07) { iSrav(c, opc, regs); return; } if (f == 0x08) { iJr(c, opc, regs); return; } if (f == 0x09) { iJalr(c, opc, regs); return; } if (f == 0x0C) { iSyscall(c, regs, cop0); return; } if (f == 0x0D) { iBreak(c, regs, cop0); return; } if (f == 0x10) { iMfhi(c, opc, regs); return; } if (f == 0x11) { iMthi(c, opc, regs); return; } if (f == 0x12) { iMflo(c, opc, regs); return; } if (f == 0x13) { iMtlo(c, opc, regs); return; } if (f == 0x18) { iMult(c, opc, regs); return; } if (f == 0x19) { iMultu(c, opc, regs); return; } if (f == 0x1A) { iDiv(c, opc, regs); return; } if (f == 0x1B) { iDivu(c, opc, regs); return; } if (f == 0x20) { iAdd(c, opc, regs, cop0); return; } if (f == 0x21) { iAddu(c, opc, regs); return; } if (f == 0x22) { iSub(c, opc, regs, cop0); return; } if (f == 0x23) { iSubu(c, opc, regs); return; } if (f == 0x24) { iAnd(c, opc, regs); return; } if (f == 0x25) { iOr(c, opc, regs); return; } if (f == 0x26) { iXor(c, opc, regs); return; } if (f == 0x27) { iNor(c, opc, regs); return; } if (f == 0x2A) { iSlt(c, opc, regs); return; } if (f == 0x2B) { iSltu(c, opc, regs); return; } illegal(c, opc, regs, cop0); return; } pub fn dispatchCop0(c: mut Cpu, opc: u32, regs: *mut[] u32, cop0: *mut[] u32) { const rs: u32 = opRs(opc); if (rs == 0x00) { iMfc0(c, opc, regs, cop0); return; } if (rs == 0x04) { iMtc0(c, opc, regs, cop0); return; } if (rs == 0x10) { iRfe(c, regs, cop0); return; } commitLoad(c, regs); } pub fn dispatch(c: mut Cpu, opc: u32, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { const p: u32 = opPrim(opc); if (p == 0x00) { dispatchSpecial(c, opc, bus, regs, cop0); return; } if (p == 0x01) { iRegimm(c, opc, regs); return; } if (p == 0x02) { iJ(c, opc, regs); return; } if (p == 0x03) { iJal(c, opc, regs); return; } if (p == 0x04) { iBeq(c, opc, regs); return; } if (p == 0x05) { iBne(c, opc, regs); return; } if (p == 0x06) { iBlez(c, opc, regs); return; } if (p == 0x07) { iBgtz(c, opc, regs); return; } if (p == 0x08) { iAddi(c, opc, regs, cop0); return; } if (p == 0x09) { iAddiu(c, opc, regs); return; } if (p == 0x0A) { iSlti(c, opc, regs); return; } if (p == 0x0B) { iSltiu(c, opc, regs); return; } if (p == 0x0C) { iAndi(c, opc, regs); return; } if (p == 0x0D) { iOri(c, opc, regs); return; } if (p == 0x0E) { iXori(c, opc, regs); return; } if (p == 0x0F) { iLui(c, opc, regs); return; } if (p == 0x10) { const sr10: u32 = cop0Read(cop0, C0_SR); if ((sr10 & 0x10000000) == 0 && (sr10 & 0x02) != 0) { commitLoad(c, regs); raiseException(c, cop0, CAUSE_CPU); return; } dispatchCop0(c, opc, regs, cop0); return; } if (p == 0x11) { const sr11: u32 = cop0Read(cop0, C0_SR); if ((sr11 & 0x20000000) == 0) { commitLoad(c, regs); raiseException(c, cop0, CAUSE_CPU); return; } commitLoad(c, regs); return; } if (p == 0x12) { const sr12: u32 = cop0Read(cop0, C0_SR); if ((sr12 & 0x40000000) == 0) { commitLoad(c, regs); raiseException(c, cop0, CAUSE_CPU); return; } iCop2Unimpl(c, opc, bus, regs, cop0); return; } if (p == 0x13) { const sr13: u32 = cop0Read(cop0, C0_SR); if ((sr13 & 0x80000000) == 0) { commitLoad(c, regs); raiseException(c, cop0, CAUSE_CPU); return; } commitLoad(c, regs); return; } if (p == 0x20) { iLb(c, opc, bus, regs); return; } if (p == 0x21) { iLh(c, opc, bus, regs, cop0); return; } if (p == 0x22) { iLwl(c, opc, bus, regs); return; } if (p == 0x23) { iLw(c, opc, bus, regs, cop0); return; } if (p == 0x24) { iLbu(c, opc, bus, regs); return; } if (p == 0x25) { iLhu(c, opc, bus, regs, cop0); return; } if (p == 0x26) { iLwr(c, opc, bus, regs); return; } if (p == 0x28) { iSb(c, opc, bus, regs, cop0); return; } if (p == 0x29) { iSh(c, opc, bus, regs, cop0); return; } if (p == 0x2A) { iSwl(c, opc, bus, regs, cop0); return; } if (p == 0x2B) { iSw(c, opc, bus, regs, cop0); return; } if (p == 0x2E) { iSwr(c, opc, bus, regs, cop0); return; } if (p == 0x30) { const sr30: u32 = cop0Read(cop0, C0_SR); if ((sr30 & 0x10000000) == 0) { commitLoad(c, regs); raiseException(c, cop0, CAUSE_CPU); return; } commitLoad(c, regs); return; } if (p == 0x31) { const sr31: u32 = cop0Read(cop0, C0_SR); if ((sr31 & 0x20000000) == 0) { commitLoad(c, regs); raiseException(c, cop0, CAUSE_CPU); return; } commitLoad(c, regs); return; } if (p == 0x32) { const sr32: u32 = cop0Read(cop0, C0_SR); if ((sr32 & 0x40000000) == 0) { commitLoad(c, regs); raiseException(c, cop0, CAUSE_CPU); return; } iCop2Lwc(c, opc, bus, regs, cop0); return; } if (p == 0x33) { const sr33: u32 = cop0Read(cop0, C0_SR); if ((sr33 & 0x80000000) == 0) { commitLoad(c, regs); raiseException(c, cop0, CAUSE_CPU); return; } commitLoad(c, regs); return; } if (p == 0x38) { const sr38: u32 = cop0Read(cop0, C0_SR); if ((sr38 & 0x10000000) == 0) { commitLoad(c, regs); raiseException(c, cop0, CAUSE_CPU); return; } commitLoad(c, regs); return; } if (p == 0x39) { const sr39: u32 = cop0Read(cop0, C0_SR); if ((sr39 & 0x20000000) == 0) { commitLoad(c, regs); raiseException(c, cop0, CAUSE_CPU); return; } commitLoad(c, regs); return; } if (p == 0x3A) { const sr3A: u32 = cop0Read(cop0, C0_SR); if ((sr3A & 0x40000000) == 0) { commitLoad(c, regs); raiseException(c, cop0, CAUSE_CPU); return; } iCop2Swc(c, opc, bus, regs, cop0); return; } if (p == 0x3B) { const sr3B: u32 = cop0Read(cop0, C0_SR); if ((sr3B & 0x80000000) == 0) { commitLoad(c, regs); raiseException(c, cop0, CAUSE_CPU); return; } commitLoad(c, regs); return; } illegal(c, opc, regs, cop0); return; } pub fn cpuIrqPending(cop0: *mut[] u32) bool { const sr: u32 = cop0Read(cop0, C0_SR); if ((sr & SR_IEC) == 0) { return false; } const cause: u32 = cop0Read(cop0, C0_CAUSE); return (sr & cause & 0x0000FF00) != 0; } pub fn step(c: mut Cpu, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { c.savedPc = c.pc; c.delaySlot = c.branch; c.branch = 0; c.branchTaken = 0; if ((c.savedPc & 3) != 0) { cop0Write(cop0, C0_BADVADDR, c.savedPc); raiseException(c, cop0, CAUSE_ADEL); return; } const pcPhys: u32 = c.savedPc & 0x1FFFFFFF; const inRam: bool = pcPhys < 0x00800000; const inBios: bool = pcPhys >= 0x1FC00000 && pcPhys < 0x1FC80000; const inDma: bool = pcPhys >= 0x1F801080 && pcPhys < 0x1F801100; const inSpu: bool = pcPhys >= 0x1F801C00 && pcPhys < 0x1F802000; if (!inRam && !inBios && !inDma && !inSpu) { cop0Write(cop0, C0_BADVADDR, c.savedPc); raiseException(c, cop0, CAUSE_IBE); return; } var fetchCyc: u64 = 0; if (inBios) { fetchCyc = 18; } if (cpuIrqPending(cop0)) { const irqOpc: u32 = busRead32(bus, c.pc); var irqCyc: u64 = fetchCyc; if ((irqOpc & 0xFE000000) == 0x4A000000) { commitLoad(c, regs); gteExec(bus.gte.ptr, irqOpc); irqCyc = irqCyc + (gteOpCycles(irqOpc) as u64); } c.cycles = c.cycles + irqCyc; c.pc = c.nextPc; c.nextPc = c.nextPc + 4; raiseException(c, cop0, CAUSE_INT); return; } const opc: u32 = busRead32(bus, c.pc); c.pc = c.nextPc; c.nextPc = c.nextPc + 4; c.cycles = c.cycles + fetchCyc + 2; dispatch(c, opc, bus, regs, cop0); if (c.loadD == 0) { c.loadV = 0; } } pub fn run(c: mut Cpu, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32, maxSteps: u32) { var i: u32 = 0; while (i < maxSteps && c.halted == 0) { step(c, bus, regs, cop0); i = i + 1; } commitLoad(c, regs); } pub fn biosPutc(ch: u32) { var buf: [1]u8 = [0; 1]; buf[0] = (ch & 0xFF) as u8; var b: []u8 = buf.asMutPtr()[0..1]; print("{b}"); } pub fn biosHook(c: mut Cpu, bus: Bus, regs: *mut[] u32) { const pcMasked: u32 = c.pc & 0x3FFFFFFF; if (pcMasked == 0xA0) { const op: u32 = gprRead(regs, 9); if (op == 0x09 || op == 0x3C) { biosPutc(gprRead(regs, 4)); } if (op == 0x3E) { var src: u32 = gprRead(regs, 4); var ch: u32 = busRead8(bus, src); while (ch != 0) { biosPutc(ch); src = src + 1; ch = busRead8(bus, src); } } } if (pcMasked == 0xB0) { const op: u32 = gprRead(regs, 9); if (op == 0x3B || op == 0x3D) { biosPutc(gprRead(regs, 4)); } if (op == 0x3F) { var src: u32 = gprRead(regs, 4); var ch: u32 = busRead8(bus, src); while (ch != 0) { biosPutc(ch); src = src + 1; ch = busRead8(bus, src); } } } } pub fn encR(op: u32, rs: u32, rt: u32, rd: u32, sa: u32, fn_: u32) u32 { return ((op & 0x3F) << 26) | ((rs & 0x1F) << 21) | ((rt & 0x1F) << 16) | ((rd & 0x1F) << 11) | ((sa & 0x1F) << 6) | (fn_ & 0x3F); } pub fn encI(op: u32, rs: u32, rt: u32, imm: u32) u32 { return ((op & 0x3F) << 26) | ((rs & 0x1F) << 21) | ((rt & 0x1F) << 16) | (imm & 0xFFFF); } pub fn encJ(op: u32, target: u32) u32 { return ((op & 0x3F) << 26) | (target & 0x03FFFFFF); }