SearchA-ZP › PlayStation

PlayStation

1994 Public domain Online

This is a Sony PlayStation main CPU running in your browser: a from-scratch MIPS R3000A (MIPS I) interpreter, self-hosted with no CDN. The PlayStation's processor is an LSI CoreWare CW33300 (an R3000A); this core executes genuine MIPS machine code — the R/I/J instruction formats, the branch delay slot, HI/LO and the COP0 system registers — and runs open homebrew MIPS demos that draw into a framebuffer. It plugs into the shared in-frame debugger for the R3000A: live registers r0-r31 + PC/HI/LO, side-effect-free memory with MIPS disassembly, single-instruction step, execution breakpoints and memory watchpoints.

Note: this integration emulates the R3000A CPU only. The rest of the PlayStation hardware (the GTE, the GPU, the SPU, the CD-ROM) and the Sony boot ROM are not emulated, so it boots homebrew MIPS demos rather than the SCEI boot animation.

Runs on: Web browser

PlayStation Online Emulator

Play PlayStation using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
R3000A Boot DemoPlayStationSony PlayStationopenOpen ⛶
Gamepad TestPlayStationSony PlayStationopenOpen ⛶
Copper BarsPlayStationSony PlayStationopenOpen ⛶

Machines emulated

Chips

Notes

Embedding

The PlayStation core here is a from-scratch R3000A (MIPS I) interpreter written in JavaScript (psx-core.js). The PS1's main CPU is a MIPS R3000A (an LSI CoreWare CW33300); this file emulates that CPU — the R/I/J instruction formats, the branch delay slot, HI/LO and the COP0 system-control registers — so it executes genuine MIPS machine code. It runs the homebrew MIPS demos in psx-programs.js, each of which draws into a linear framebuffer in main RAM that the boot script blits to a canvas. Everything is self-hosted, no CDN.

var cpu = new PSXCore();
cpu.loadProgram(PSX_PROGRAMS.boot);   // { load, entry, words[] } of MIPS words
cpu.step();                          // execute exactly one R3000A instruction
cpu.io.pad = bits;                     // host writes the gamepad latch (read at 0x1F801040)
cpu.io.frame++;                        // host frame counter (read at 0x1F801044)

Rendering is manual. The demos write RGBA8888 pixels to a 320×240 framebuffer at virtual address 0x80080000 (physical 0x00080000 in the 2 MB main RAM). Each animation frame the boot script wraps that heap region in a Uint8ClampedArray, pushes it through an ImageData, and scales it onto the visible canvas:

var px = new Uint8ClampedArray(cpu.ram.buffer, 0x80000, 320*240*4);
img.data.set(px); bctx.putImageData(img, 0, 0);
ctx.drawImage(back, 0,0,320,240, 0,0, canvas.width, canvas.height);

Input is a memory-mapped latch. The boot script keeps the pressed buttons in a JS bitmask and exposes it at 0x1F801040; a demo reads it with lw. Bit 0 Up, 1 Down, 2 Left, 3 Right, 4 Triangle, 5 Circle, 6 Cross, 7 Square, 8 L1, 9 L2, 10 R1, 11 R2, 12 Start, 13 Select (pressed = 1). The boot script owns the requestAnimationFrame loop and runs a large instruction budget per frame, so pausing simply stops calling step.

Debugger integration

The debugger plugs straight into the live R3000A, because the core is an ordinary JS object whose whole state — the register file, HI/LO, the COP0 registers and all of RAM — is directly readable and writable from the host page. No hooks in the hot path are needed: the plug-in (psx-debug.js) samples these fields when its window is open, and calls step() once per Step.

1 · A new CPU decoder. The R3000A is a MIPS I core, which the debugger did not previously know, so this integration adds debugger/src/cpus/r3000.js — a MIPS I disassembler registered as decoder 'r3000'. It reads a little-endian 32-bit word and decodes the three formats (R-type ADD/SUB/AND/OR/SLT/SLL/SRL/JR/…, I-type ADDIU/ORI/LUI/LW/SW/LB/BEQ/BNE/…, J-type J/JAL) plus the COP0 (MFC0/MTC0/RFE) and COP2/GTE escapes; anything illegal decodes as a 4-byte .word. The plug-in points cpu.decoder and every memory chip at 'r3000'.

// psx-core.js — one real R3000A instruction, delay-slot correct
PSXCore.prototype.step = function () {
  var pc = this.pc >>> 0;
  var instr = this.read32(pc);
  this.pc = this.nextpc;                // the fetched op may retarget nextpc…
  this.nextpc = (this.nextpc + 4) >>> 0;  // …so its delay slot runs before the target
  this.exec(instr, pc);
  this.gpr[0] = 0;                    // r0 is hard-wired zero
};

2 · What is REAL here. Because step() executes exactly one MIPS instruction over the live register file, this build has:

  • Real registers — the 32 general-purpose registers r0-r31 (o32 ABI names $zero $at $v0 … $ra), plus PC, HI and LO, read and written live. r0 is shown read-only.
  • Real single-instruction stepStep i calls cpu.step(), which fetches, executes and advances PC by one instruction (honouring the branch delay slot); PC and the registers change by exactly one instruction per click.
  • Side-effect-free memory — the hex / disasm views read RAM (and the framebuffer) directly through peek8, which never disturbs the machine; the two memory-mapped input words return the latched state without clearing it.
  • Execution breakpoints & memory watchpoints — implemented host-side in the run loop (see below), so they cost nothing when the debugger is closed.

3 · Breakpoints and watchpoints. When nothing is armed the loop runs a big instruction budget per frame at full speed. As soon as a breakpoint or watchpoint is set the loop switches to single-stepping: after each step() it compares the live PC against the breakpoint set, and each watched address's byte against its last sampled value, pausing on the first hit. This is a pure host-side guard that only runs while armed.

while (budget-->0) {
  if (bps[cpu.pc >>> 0]) { paused = true; break; }   // execution breakpoint
  cpu.step();
  for (var a in wps) if (cpu.peek8(a) !== last[a]) { paused = true; ... }  // watchpoint
}

Architecture

The Sony PlayStation (1994) was the first CD-based console to sell in the tens of millions and defined a generation of 3D gaming. Its heart is a MIPS R3000A 32-bit RISC CPU running at 33.87 MHz (an LSI CoreWare CW33300), packaged with a hardware GTE geometry coprocessor for 3D maths and an MDEC motion-decoder, alongside a 2D GPU rasteriser (1 MB VRAM), a 24-channel SPU, a double-speed CD-ROM controller and 2 MB of main RAM.

This integration emulates the CPU. The R3000A is a clean MIPS I machine:

  • 32 general-purpose registers (r0 hard-wired to zero), plus HI/LO for multiply/divide results and a program counter, with a one-slot branch delay: the instruction after every branch or jump always executes.
  • Three instruction formats — R-type (register-register ALU and shifts, JR/JALR), I-type (immediate ALU, loads/stores, conditional branches) and J-type (J/JAL). All instructions are a fixed 4 bytes, little-endian.
  • COP0, the system control coprocessor (status register, cause, EPC), reachable with MFC0/MTC0/RFE; COP2 is the GTE.

The demos run in a small model of the PS1 address space — 2 MB of main RAM (KUSEG at 0x00000000, mirrored through KSEG0 at 0x80000000), a linear RGBA framebuffer at 0x80080000, and two memory-mapped input words (a gamepad latch and a host frame counter). The full PlayStation hardware — the GTE, the GPU, the SPU, the CD-ROM and the Sony boot ROM — is not emulated, which is why this boots homebrew MIPS demos rather than the SCEI boot animation; the deliverable is a genuine, inspectable R3000A.