Something went wrong. Try again.
A Playstation One (PS1) emulator fully written in Jam
Something went wrong. Try again.
jamstation tests.jam
22 kB · 631 lines
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632// SPDX-License-Identifier: MIT// Copyright (c) 2026 Raphael Amorim
const { assert } = import("test");
const { Bus, createBus, busWrite32} = import("bus");
const { freshCpu, freshCpuAt, createRegFile, gprRead, gprWrite, cop0Read, cop0Write, run, step, biosHook, commitLoad, encR, encI, encJ} = import("cpu");
const { Gpu } = import("gpu");const { createGte, gteExec, gteDivide, gteDataRead, gteDataWrite, gteCtrlWrite, gteCtrlRead, gteOpCycles } = import("gte");const { Cdrom } = import("cdrom");const { Disc, createDisc, discOpen } = import("disc");const { createDma, dmaUpdate } = import("dma");const { createPad, padUpdate } = import("pad");const { createSpu, createSpuRam, spuUpdate } = import("spu");const { Sio1 } = import("sio1");const { createIrq, irqRaise, IC_VBLANK } = import("irq");const { Vec } = import("std/collections");const { Timer } = import("timer");const { Mcd } = import("mcd");const { Mdec } = import("mdec");
const { File, exists } = import("std/fs");
const C0_SR: u32 = 12;const SR_BEV: u32 = 0x00400000;
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 Harness = struct { cpu: Cpu, regs: Vec(u32), cop0: Vec(u32), bus: Bus,};
fn createHarness() Harness { var regs: Vec(u32) = createRegFile(32); var cop0: Vec(u32) = createRegFile(32); var bus: Bus = createBus(); var cpu: Cpu = freshCpuAt(0x80000000); var h: Harness = Harness { cpu: cpu, regs: regs, cop0: cop0, bus: bus }; return h;}
fn placeOp(bus: Bus, cop0: Vec(u32), addr: u32, opc: u32) { busWrite32(bus, cop0.ptr, addr, opc);}
fn tOriImm() u32 { var h: Harness = createHarness(); placeOp(h.bus, h.cop0.ptr, 0x80000000, encI(0x0D, 0, 8, 0x1234)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 4); const v: u32 = gprRead(h.regs.ptr, 8); return v;}
fn tLuiOri() u32 { var h: Harness = createHarness(); placeOp(h.bus, h.cop0.ptr, 0x80000000, encI(0x0F, 0, 9, 0xABCD)); placeOp(h.bus, h.cop0.ptr, 0x80000004, encI(0x0D, 9, 9, 0xEF01)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 4); const v: u32 = gprRead(h.regs.ptr, 9); return v;}
fn tAdduChain() u32 { var h: Harness = createHarness(); placeOp(h.bus, h.cop0.ptr, 0x80000000, encI(0x09, 0, 8, 7)); placeOp(h.bus, h.cop0.ptr, 0x80000004, encI(0x09, 0, 9, 5)); placeOp(h.bus, h.cop0.ptr, 0x80000008, encR(0x00, 8, 9, 10, 0, 0x21)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 8); const v: u32 = gprRead(h.regs.ptr, 10); return v;}
fn tSltSigned() u32 { var h: Harness = createHarness(); placeOp(h.bus, h.cop0.ptr, 0x80000000, encI(0x09, 0, 8, 0xFFFF)); placeOp(h.bus, h.cop0.ptr, 0x80000004, encI(0x09, 0, 9, 1)); placeOp(h.bus, h.cop0.ptr, 0x80000008, encR(0x00, 8, 9, 10, 0, 0x2A)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 8); const v: u32 = gprRead(h.regs.ptr, 10); return v;}
fn tSltuSigned() u32 { var h: Harness = createHarness(); placeOp(h.bus, h.cop0.ptr, 0x80000000, encI(0x09, 0, 8, 0xFFFF)); placeOp(h.bus, h.cop0.ptr, 0x80000004, encI(0x09, 0, 9, 1)); placeOp(h.bus, h.cop0.ptr, 0x80000008, encR(0x00, 8, 9, 10, 0, 0x2B)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 8); const v: u32 = gprRead(h.regs.ptr, 10); return v;}
fn tShiftSra() u32 { var h: Harness = createHarness(); placeOp(h.bus, h.cop0.ptr, 0x80000000, encI(0x0F, 0, 8, 0x8000)); placeOp(h.bus, h.cop0.ptr, 0x80000004, encR(0x00, 0, 8, 9, 2, 0x03)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 4); const v: u32 = gprRead(h.regs.ptr, 9); return v;}
fn tAndOr() u32 { var h: Harness = createHarness(); placeOp(h.bus, h.cop0.ptr, 0x80000000, encI(0x0D, 0, 8, 0xF0F0)); placeOp(h.bus, h.cop0.ptr, 0x80000004, encI(0x0D, 0, 9, 0x0FF0)); placeOp(h.bus, h.cop0.ptr, 0x80000008, encR(0x00, 8, 9, 10, 0, 0x24)); placeOp(h.bus, h.cop0.ptr, 0x8000000C, encR(0x00, 8, 9, 11, 0, 0x25)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 8); const a: u32 = gprRead(h.regs.ptr, 10); const o: u32 = gprRead(h.regs.ptr, 11); return (a << 16) | o;}
fn tBranchDelay() u32 { var h: Harness = createHarness(); placeOp(h.bus, h.cop0.ptr, 0x80000000, encI(0x0D, 0, 8, 7)); placeOp(h.bus, h.cop0.ptr, 0x80000004, encI(0x04, 8, 8, 2)); placeOp(h.bus, h.cop0.ptr, 0x80000008, encI(0x0D, 0, 9, 1)); placeOp(h.bus, h.cop0.ptr, 0x8000000C, encI(0x0D, 0, 10, 0xFFFF)); placeOp(h.bus, h.cop0.ptr, 0x80000010, encI(0x0D, 0, 11, 0x5555)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 6); const t1: u32 = gprRead(h.regs.ptr, 9); const t2: u32 = gprRead(h.regs.ptr, 10); const t3: u32 = gprRead(h.regs.ptr, 11); return (t1 << 24) | (t2 << 16) | t3;}
fn tJumpDelay() u32 { var h: Harness = createHarness(); const tgt: u32 = (0x80000020 >> 2) & 0x03FFFFFF; placeOp(h.bus, h.cop0.ptr, 0x80000000, encJ(0x02, tgt)); placeOp(h.bus, h.cop0.ptr, 0x80000004, encI(0x0D, 0, 8, 1)); placeOp(h.bus, h.cop0.ptr, 0x80000008, encI(0x0D, 0, 9, 0xFFFF)); placeOp(h.bus, h.cop0.ptr, 0x80000020, encI(0x0D, 0, 9, 0xAA)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 6); const t0: u32 = gprRead(h.regs.ptr, 8); const t1: u32 = gprRead(h.regs.ptr, 9); return (t0 << 16) | t1;}
fn tJalJr() u32 { var h: Harness = createHarness(); const tgt: u32 = (0x80000020 >> 2) & 0x03FFFFFF; placeOp(h.bus, h.cop0.ptr, 0x80000000, encJ(0x03, tgt)); placeOp(h.bus, h.cop0.ptr, 0x80000004, encI(0x09, 0, 0, 0)); placeOp(h.bus, h.cop0.ptr, 0x80000008, encI(0x0D, 0, 11, 0x33)); placeOp(h.bus, h.cop0.ptr, 0x80000020, encI(0x0D, 0, 8, 0x42)); placeOp(h.bus, h.cop0.ptr, 0x80000024, encR(0x00, 31, 0, 0, 0, 0x08)); placeOp(h.bus, h.cop0.ptr, 0x80000028, encI(0x09, 0, 0, 0)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 12); const t0: u32 = gprRead(h.regs.ptr, 8); const t3: u32 = gprRead(h.regs.ptr, 11); return (t0 << 16) | t3;}
fn tLwSw() u32 { var h: Harness = createHarness(); placeOp(h.bus, h.cop0.ptr, 0x80000000, encI(0x0F, 0, 8, 0xDEAD)); placeOp(h.bus, h.cop0.ptr, 0x80000004, encI(0x0D, 8, 8, 0xBEEF)); placeOp(h.bus, h.cop0.ptr, 0x80000008, encI(0x0F, 0, 9, 0x8000)); placeOp(h.bus, h.cop0.ptr, 0x8000000C, encI(0x0D, 9, 9, 0x0100)); placeOp(h.bus, h.cop0.ptr, 0x80000010, encI(0x2B, 9, 8, 0)); placeOp(h.bus, h.cop0.ptr, 0x80000014, encI(0x23, 9, 10, 0)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 8); const v: u32 = gprRead(h.regs.ptr, 10); return v;}
fn tLbSignExt() u32 { var h: Harness = createHarness(); placeOp(h.bus, h.cop0.ptr, 0x80000000, encI(0x0D, 0, 8, 0x80)); placeOp(h.bus, h.cop0.ptr, 0x80000004, encI(0x0F, 0, 9, 0x8000)); placeOp(h.bus, h.cop0.ptr, 0x80000008, encI(0x0D, 9, 9, 0x0100)); placeOp(h.bus, h.cop0.ptr, 0x8000000C, encI(0x28, 9, 8, 0)); placeOp(h.bus, h.cop0.ptr, 0x80000010, encI(0x20, 9, 10, 0)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 8); const v: u32 = gprRead(h.regs.ptr, 10); return v;}
fn tMultSigned() u64 { var h: Harness = createHarness(); placeOp(h.bus, h.cop0.ptr, 0x80000000, encI(0x09, 0, 8, 0xFFFF)); placeOp(h.bus, h.cop0.ptr, 0x80000004, encI(0x09, 0, 9, 2)); placeOp(h.bus, h.cop0.ptr, 0x80000008, encR(0x00, 8, 9, 0, 0, 0x18)); placeOp(h.bus, h.cop0.ptr, 0x8000000C, encR(0x00, 0, 0, 10, 0, 0x10)); placeOp(h.bus, h.cop0.ptr, 0x80000010, encR(0x00, 0, 0, 11, 0, 0x12)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 8); const hi: u64 = gprRead(h.regs.ptr, 10) as u64; const lo: u64 = gprRead(h.regs.ptr, 11) as u64; return (hi << 32) | lo;}
fn tDivu() u32 { var h: Harness = createHarness(); placeOp(h.bus, h.cop0.ptr, 0x80000000, encI(0x09, 0, 8, 100)); placeOp(h.bus, h.cop0.ptr, 0x80000004, encI(0x09, 0, 9, 7)); placeOp(h.bus, h.cop0.ptr, 0x80000008, encR(0x00, 8, 9, 0, 0, 0x1B)); placeOp(h.bus, h.cop0.ptr, 0x8000000C, encR(0x00, 0, 0, 10, 0, 0x12)); placeOp(h.bus, h.cop0.ptr, 0x80000010, encR(0x00, 0, 0, 11, 0, 0x10)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 8); const q: u32 = gprRead(h.regs.ptr, 10); const r: u32 = gprRead(h.regs.ptr, 11); return (q << 16) | r;}
fn tSumLoop() u32 { var h: Harness = createHarness(); placeOp(h.bus, h.cop0.ptr, 0x80000000, encI(0x09, 0, 8, 0)); placeOp(h.bus, h.cop0.ptr, 0x80000004, encI(0x09, 0, 9, 5)); placeOp(h.bus, h.cop0.ptr, 0x80000008, encR(0x00, 8, 9, 8, 0, 0x21)); placeOp(h.bus, h.cop0.ptr, 0x8000000C, encI(0x09, 9, 9, 0xFFFF)); placeOp(h.bus, h.cop0.ptr, 0x80000010, encI(0x05, 9, 0, 0xFFFD)); placeOp(h.bus, h.cop0.ptr, 0x80000014, encI(0x09, 0, 0, 0)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 64); const v: u32 = gprRead(h.regs.ptr, 8); return v;}
fn tCop0Roundtrip() u32 { var h: Harness = createHarness(); placeOp(h.bus, h.cop0.ptr, 0x80000000, encI(0x0F, 0, 8, 0xDEAD)); placeOp(h.bus, h.cop0.ptr, 0x80000004, encR(0x10, 4, 8, 12, 0, 0)); placeOp(h.bus, h.cop0.ptr, 0x80000008, encR(0x10, 0, 9, 12, 0, 0)); placeOp(h.bus, h.cop0.ptr, 0x8000000C, encI(0x09, 0, 0, 0)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 8); const v: u32 = gprRead(h.regs.ptr, 9); return v;}
fn tSyscallVector() u32 { var h: Harness = createHarness(); cop0Write(h.cop0.ptr, C0_SR, SR_BEV); placeOp(h.bus, h.cop0.ptr, 0x80000000, encR(0x00, 0, 0, 0, 0, 0x0C)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 1); const pc: u32 = h.cpu.pc; return pc;}
fn tLoadDelay() u32 { var h: Harness = createHarness(); placeOp(h.bus, h.cop0.ptr, 0x80000000, encI(0x0F, 0, 29, 0x8000)); placeOp(h.bus, h.cop0.ptr, 0x80000004, encI(0x0D, 29, 29, 0x0100)); placeOp(h.bus, h.cop0.ptr, 0x80000008, encI(0x0D, 0, 9, 42)); placeOp(h.bus, h.cop0.ptr, 0x8000000C, encI(0x2B, 29, 9, 0)); placeOp(h.bus, h.cop0.ptr, 0x80000010, encI(0x23, 29, 8, 0)); placeOp(h.bus, h.cop0.ptr, 0x80000014, encI(0x0D, 8, 10, 0)); placeOp(h.bus, h.cop0.ptr, 0x80000018, encI(0x0D, 8, 11, 0)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 16); const t2: u32 = gprRead(h.regs.ptr, 10); const t3: u32 = gprRead(h.regs.ptr, 11); return (t2 << 16) | t3;}
fn tLwlLwr() u32 { var h: Harness = createHarness(); placeOp(h.bus, h.cop0.ptr, 0x80000000, encI(0x0F, 0, 8, 0x1122)); placeOp(h.bus, h.cop0.ptr, 0x80000004, encI(0x0D, 8, 8, 0x3344)); placeOp(h.bus, h.cop0.ptr, 0x80000008, encI(0x0F, 0, 9, 0xAABB)); placeOp(h.bus, h.cop0.ptr, 0x8000000C, encI(0x0D, 9, 9, 0xCCDD)); placeOp(h.bus, h.cop0.ptr, 0x80000010, encI(0x0F, 0, 29, 0x8000)); placeOp(h.bus, h.cop0.ptr, 0x80000014, encI(0x0D, 29, 29, 0x0100)); placeOp(h.bus, h.cop0.ptr, 0x80000018, encI(0x2B, 29, 8, 0)); placeOp(h.bus, h.cop0.ptr, 0x8000001C, encI(0x2B, 29, 9, 4)); placeOp(h.bus, h.cop0.ptr, 0x80000020, encI(0x0D, 0, 10, 0)); placeOp(h.bus, h.cop0.ptr, 0x80000024, encI(0x22, 29, 10, 6)); placeOp(h.bus, h.cop0.ptr, 0x80000028, encI(0x26, 29, 10, 3)); placeOp(h.bus, h.cop0.ptr, 0x8000002C, encI(0x09, 0, 0, 0)); run(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr, 20); const v: u32 = gprRead(h.regs.ptr, 10); return v;}
tfn psxOriImm() { assert(tOriImm(), 0x1234); }tfn psxLuiOri() { assert(tLuiOri(), 0xABCDEF01); }tfn psxAdduChain() { assert(tAdduChain(), 12); }tfn psxSltSigned() { assert(tSltSigned(), 1); }tfn psxSltuSigned() { assert(tSltuSigned(), 0); }tfn psxShiftSra() { assert(tShiftSra(), 0xE0000000); }tfn psxAndOr() { assert(tAndOr(), 0x00F0FFF0); }tfn psxBranchDelay() { assert(tBranchDelay(), 0x01005555); }tfn psxJumpDelay() { assert(tJumpDelay(), 0x000100AA); }tfn psxJalJr() { assert(tJalJr(), 0x00420033); }tfn psxLwSw() { assert(tLwSw(), 0xDEADBEEF); }tfn psxLbSignExt() { assert(tLbSignExt(), 0xFFFFFF80); }tfn psxMultSigned() { assert(tMultSigned(), 0xFFFFFFFFFFFFFFFE); }tfn psxDivu() { assert(tDivu(), 0x000E0002); }tfn psxSumLoop() { assert(tSumLoop(), 15); }tfn psxCop0Roundtrip() { assert(tCop0Roundtrip(), 0xDEAD0000); }tfn psxSyscallVector() { assert(tSyscallVector(), 0xBFC00180); }tfn psxLoadDelay() { assert(tLoadDelay(), 0x0000002A); }tfn psxLwlLwr() { assert(tLwlLwr(), 0xBBCCDD11); }
fn gteSetIdentity(g: *mut[] u32) { gteCtrlWrite(g, 0, 0x00001000); gteCtrlWrite(g, 1, 0x00000000); gteCtrlWrite(g, 2, 0x00001000); gteCtrlWrite(g, 3, 0x00000000); gteCtrlWrite(g, 4, 0x00001000);}
fn tGteRtpsMac() u32 { var g: Vec(u32) = createGte(); gteSetIdentity(g.ptr); gteDataWrite(g.ptr, 0, (200 << 16) | 100); gteDataWrite(g.ptr, 1, 300); gteExec(g.ptr, 0x4A080001); const mac1: u32 = gteDataRead(g.ptr, 25); return mac1;}
fn tGteRtpsIr() u32 { var g: Vec(u32) = createGte(); gteSetIdentity(g.ptr); gteDataWrite(g.ptr, 0, (200 << 16) | 100); gteDataWrite(g.ptr, 1, 300); gteExec(g.ptr, 0x4A080001); const ir3: u32 = gteDataRead(g.ptr, 11); return ir3;}
fn tGteIrSext() u32 { var g: Vec(u32) = createGte(); gteDataWrite(g.ptr, 9, 0x0000FFFF); const v: u32 = gteDataRead(g.ptr, 9); return v;}
fn tGteDivide() u32 { var g: Vec(u32) = createGte(); const q: u32 = gteDivide(g.ptr, 0x800, 0x1000); return q;}
fn tGteDivideOvf() u32 { var g: Vec(u32) = createGte(); const q: u32 = gteDivide(g.ptr, 0x2000, 0x1000); return q;}
fn tGteSqrFlag() u32 { var g: Vec(u32) = createGte(); gteDataWrite(g.ptr, 9, 0x7FFF); gteExec(g.ptr, 0x4A080028); const f: u32 = gteCtrlRead(g.ptr, 31); return f;}
fn tGteDivideFlag() u32 { var g: Vec(u32) = createGte(); const q: u32 = gteDivide(g.ptr, 0x2000, 0x1000); const f: u32 = gteCtrlRead(g.ptr, 31); return f;}
fn tGteNoFlag() u32 { var g: Vec(u32) = createGte(); gteDataWrite(g.ptr, 9, 100); gteExec(g.ptr, 0x4A080028); const f: u32 = gteCtrlRead(g.ptr, 31); return f;}
fn tGteRtpsClampIrZ() u32 { var g: Vec(u32) = createGte(); gteSetIdentity(g.ptr); gteDataWrite(g.ptr, 0, 0); gteDataWrite(g.ptr, 1, 256); gteExec(g.ptr, 0x4A000001); const f: u32 = gteCtrlRead(g.ptr, 31); return f;}
fn tGteMvmvaNestedOvf() u32 { var g: Vec(u32) = createGte(); gteCtrlWrite(g.ptr, 5, 0x7FFFFFFF); gteCtrlWrite(g.ptr, 0, 0x80007FFF); gteCtrlWrite(g.ptr, 1, 0); gteDataWrite(g.ptr, 0, 0x7FFF7FFF); gteDataWrite(g.ptr, 1, 0); gteExec(g.ptr, 0x4A080012); const f: u32 = gteCtrlRead(g.ptr, 31) & 0x40000000; return f;}
fn tGteIrgbNeg() u32 { var g: Vec(u32) = createGte(); gteDataWrite(g.ptr, 9, 0x0000FFFF); gteDataWrite(g.ptr, 10, 0x0000FFFF); gteDataWrite(g.ptr, 11, 0x0000FFFF); return gteDataRead(g.ptr, 28);}
fn tGteIrgbPos() u32 { var g: Vec(u32) = createGte(); gteDataWrite(g.ptr, 9, 0x0F80); gteDataWrite(g.ptr, 10, 0x0080); gteDataWrite(g.ptr, 11, 0x0000); return gteDataRead(g.ptr, 28);}
fn tGteRtptDqGate() u32 { var g: Vec(u32) = createGte(); gteSetIdentity(g.ptr); gteCtrlWrite(g.ptr, 26, 2); gteCtrlWrite(g.ptr, 27, 0x1000); gteCtrlWrite(g.ptr, 28, 0); gteDataWrite(g.ptr, 0, 0); gteDataWrite(g.ptr, 1, 16); gteDataWrite(g.ptr, 2, 0); gteDataWrite(g.ptr, 3, 16); gteDataWrite(g.ptr, 4, 0); gteDataWrite(g.ptr, 5, 0x1000); gteExec(g.ptr, 0x4A000030); return gteCtrlRead(g.ptr, 31) & 0x1000;}
fn tGteNcdsRgbSat() u32 { var g: Vec(u32) = createGte(); gteCtrlWrite(g.ptr, 21, 0x00010000); gteDataWrite(g.ptr, 8, 0x7FFF); gteExec(g.ptr, 0x4A000013); return gteCtrlRead(g.ptr, 31) & 0x00380000;}
fn tGteDqaWidth() u32 { var g: Vec(u32) = createGte(); gteSetIdentity(g.ptr); gteCtrlWrite(g.ptr, 26, 0x1000); gteCtrlWrite(g.ptr, 27, 0x00010001); gteCtrlWrite(g.ptr, 28, 0); gteDataWrite(g.ptr, 0, 0); gteDataWrite(g.ptr, 1, 0x1000); gteExec(g.ptr, 0x4A000001); return gteDataRead(g.ptr, 8);}
tfn psxGteSqrFlag() { assert(tGteSqrFlag(), 0x81000000); }tfn psxGteDivideFlag() { assert(tGteDivideFlag(), 0x80020000); }tfn psxGteNoFlag() { assert(tGteNoFlag(), 0); }tfn psxGteClampIrZ() { assert(tGteRtpsClampIrZ(), 0); }tfn psxGteMvmvaNestedOvf() { assert(tGteMvmvaNestedOvf(), 0x40000000); }
tfn psxGteRtpsMac() { assert(tGteRtpsMac(), 100); }tfn psxGteRtpsIr() { assert(tGteRtpsIr(), 300); }tfn psxGteIrSext() { assert(tGteIrSext(), 0xFFFFFFFF); }tfn psxGteDivide() { assert(tGteDivide(), 0x8000); }tfn psxGteDivideOvf() { assert(tGteDivideOvf(), 0x1FFFF); }
tfn psxGteIrgbNeg() { assert(tGteIrgbNeg(), 0); }tfn psxGteIrgbPos() { assert(tGteIrgbPos(), 0x3F); }tfn psxGteRtptDqGate() { assert(tGteRtptDqGate(), 0); }tfn psxGteNcdsRgbSat() { assert(tGteNcdsRgbSat(), 0x00200000); }tfn psxGteDqaWidth() { assert(tGteDqaWidth(), 16); }
const C0_PRID: u32 = 15;
fn fileExists(path: []u8) bool { return exists(path);}
fn emuLoadBios(bus: Bus) i32 { var path: []u8 = "SCPH1001.BIN"; match (File.open(path)) { Some(f) { var biosPtr: *mut[] u8 = bus.bios.ptr; const got: u64 = f.read(biosPtr[0..0x80000]); f.close(); if (got != 0x80000) { return 0; } return 1; } None { return 0; } } return 0;}
const CYC_PER_SCANLINE: u32 = 2172;const SCANLINES_VDRAW: u32 = 240;const SCANLINES_TOTAL: u32 = 263;
fn emuRunOneFrame(c: mut Cpu, bus: Bus, regs: Vec(u32), cop0: Vec(u32)) { var line: u32 = 0; var lineCyc: u32 = 0; while (line < SCANLINES_TOTAL) { biosHook(c, bus, regs.ptr); const before: u64 = c.cycles; step(c, bus, regs.ptr, cop0.ptr); const delta: u32 = (c.cycles - before) as u32; bus.timer.updateCyc(bus.irq.ptr, cop0.ptr, delta); dmaUpdate(bus.dma.ptr, bus.irq.ptr, cop0.ptr); bus.cdrom.update(bus.disc.ptrMut(), bus.irq.ptr, cop0.ptr, delta); padUpdate(bus.pad.ptr, bus.irq.ptr, cop0.ptr, delta); bus.sio1.update(bus.irq.ptr, cop0.ptr, delta); spuUpdate(bus.spu.ptr, bus.spuram.ptr, bus.irq.ptr, cop0.ptr, delta); lineCyc = lineCyc + delta; if (lineCyc >= CYC_PER_SCANLINE) { lineCyc = lineCyc - CYC_PER_SCANLINE; bus.timer.hblankBegin(bus.irq.ptr, cop0.ptr); bus.timer.hblankEnd(); if (line == SCANLINES_VDRAW) { bus.timer.vblankBegin(bus.irq.ptr, cop0.ptr); irqRaise(bus.irq.ptr, cop0.ptr, IC_VBLANK); } line = line + 1; } } commitLoad(c, regs.ptr); bus.timer.vblankEnd();}
fn emuRunFrames(c: mut Cpu, bus: Bus, regs: Vec(u32), cop0: Vec(u32), frames: u32) { var i: u32 = 0; while (i < frames) { emuRunOneFrame(c, bus, regs.ptr, cop0.ptr); i = i + 1; }}
fn emuBoot(c: mut Cpu, bus: Bus, regs: Vec(u32), cop0: Vec(u32)) i32 { cop0Write(cop0.ptr, C0_SR, 0x10900000); cop0Write(cop0.ptr, C0_PRID, 0x00000002); return emuLoadBios(bus);}
fn emuPcInRamOrScratchpad(pc: u32) bool { const top: u32 = pc & 0xFF000000; if (top == 0x80000000) { return true; } if (top == 0xA0000000) { return true; } if (top == 0x00000000) { return true; } return false;}
tfn emuBiosShellBoots() { var regs: Vec(u32) = createRegFile(32); var cop0: Vec(u32) = createRegFile(32); var bus: Bus = createBus(); var c: Cpu = freshCpu(); if (emuBoot(c, bus, regs.ptr, cop0.ptr) == 0) { return; } emuRunFrames(c, bus, regs.ptr, cop0.ptr, 60); assert(emuPcInRamOrScratchpad(c.pc), true);}
tfn emuDiscBootsExec() { var diskPath: []u8 = "game.bin"; if (!fileExists(diskPath)) { return; } var regs: Vec(u32) = createRegFile(32); var cop0: Vec(u32) = createRegFile(32); var bus: Bus = createBus(); var c: Cpu = freshCpu(); if (emuBoot(c, bus, regs.ptr, cop0.ptr) == 0) { return; } discOpen(bus.disc.ptrMut(), diskPath); emuRunFrames(c, bus, regs.ptr, cop0.ptr, 120); assert(emuPcInRamOrScratchpad(c.pc), true);}
fn tGteCycRtps() u32 { return gteOpCycles(0x4A000001); }fn tGteCycNcdt() u32 { return gteOpCycles(0x4A000016); }fn tGteCycRtpt() u32 { return gteOpCycles(0x4A000030); }fn tGteCycNcct() u32 { return gteOpCycles(0x4A00003F); }fn tGteCycDflt() u32 { return gteOpCycles(0x4A000000); }
fn tCycRam() u32 { var h: Harness = createHarness(); placeOp(h.bus, h.cop0.ptr, 0x80000000, encI(0x0D, 0, 8, 0x1234)); step(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr); const v: u32 = h.cpu.cycles as u32; return v;}
fn tCycBios() u32 { var regs: Vec(u32) = createRegFile(32); var cop0: Vec(u32) = createRegFile(32); var bus: Bus = createBus(); var cpu: Cpu = freshCpuAt(0xBFC00000); var h: Harness = Harness { cpu: cpu, regs: regs, cop0: cop0, bus: bus }; step(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr); const v: u32 = h.cpu.cycles as u32; return v;}
fn tCycGteRtps() u32 { var h: Harness = createHarness(); cop0Write(h.cop0.ptr, 12, 0x40000000); placeOp(h.bus, h.cop0.ptr, 0x80000000, 0x4A000001); step(h.cpu, h.bus, h.regs.ptr, h.cop0.ptr); const v: u32 = h.cpu.cycles as u32; return v;}
tfn psxCycGteRtps() { assert(tGteCycRtps(), 15); }tfn psxCycGteNcdt() { assert(tGteCycNcdt(), 44); }tfn psxCycGteRtpt() { assert(tGteCycRtpt(), 23); }tfn psxCycGteNcct() { assert(tGteCycNcct(), 39); }tfn psxCycGteDflt() { assert(tGteCycDflt(), 2); }tfn psxCycRam() { assert(tCycRam(), 2); }tfn psxCycBios() { assert(tCycBios(), 20); }tfn psxCycGteStep() { assert(tCycGteRtps(), 15); }