Something went wrong. Try again.
A Playstation One (PS1) emulator fully written in Jam
Something went wrong. Try again.
jamstation cpu.jam
36 kB · 1196 lines
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197// 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);}