CHIP-8 Emulator
A web-based emulator for the classic Chip-8 virtual machine. The core of the emulator is written in JavaScript, with HTML and CSS for the user interface, and Express for the backend. The project is designed in a way to closely resemble the original Chip-8 architecture, with configurable quirks to support a wider range of Chip-8 ROMs.
Features
ROM Management
- Built-in Chip8Archive ROM library, offering easy access to public domain Chip-8 ROMs.
- Drag‑and‑drop import for custom ROM files.
Debugging
- Realtime visualization of the internal state of the emulator including the memory, registers, timers, pointers, and stack.
- Pause, resume, single‑step execution (instruction or cycle) for ROM debugging.
Configurable Quirks
-
Support for shift quirks, load-store quirks, jump quirks, clipping/wrapping quirks, logic quirks, and v-blank quirks. See chip-8.github.io/database/#options for more information.
-
Shift Quirks
case 8XY6:
if (!this.quirkShift) {
Vx = Vy;
}
var result = Vx >> 1;
this._set_carry(instruction.x, result, Vy & 0x1);
return true;
case 8XYE:
if (!this.quirkShift) {
Vx = Vy;
}
var result = Vx << 1;
this._set_carry(instruction.x, result, (Vy >> 7) & 0x1);
return true;
- Load-Store Quirks
case FX55:
for (let i = 0; i <= instruction.x; i++) {
this.memory.set(
(this.indexRegister + i) & 0xfff,
this.registers.get(i)
);
}
this.indexRegister += i_increment;
return true;
case FX65:
for (let i = 0; i <= instruction.x; i++) {
this.registers.set(
i,
this.memory.get((this.indexRegister + i) & 0xfff)
);
}
this.indexRegister += i_increment;
return true;
- Jump Quirks
case BNNN:
if (this.quirkJump) {
this.programCounter = instruction.nnn + Vx;
} else {
this.programCounter =
instruction.nnn + this.registers.get(0);
}
return true;
- Clip/Wrap Quirks
case DXYN:
let screenX = Vx % this.screen.renderWidth;
let screenY = Vy % this.screen.renderHeight;
let spriteHeight = instruction.n;
let spriteWidth = 8;
let yCondition = this.quirkWrap
? (y) => y < spriteHeight
: (y) => y < spriteHeight && y + screenY < this.screen.renderHeight;
let xCondition = this.quirkWrap
? (x) => x < spriteWidth
: (x) => x < spriteWidth && x + screenX < this.screen.renderWidth;
this.registers.set(0xf, 0);
for (let y = 0; yCondition(y); y++) {
let pixel_row = this.memory.get(this.indexRegister + y);
for (let x = 0; xCondition(x); x++) {
this.registers.set(
0xf,
this.screen.setPixel(
x + screenX,
y + screenY,
(pixel_row >> 7) & 0b1
) || this.registers.get(0xf)
);
pixel_row <<= 1;
}
}
return true;
- Logic Quirks
case 8XY1:
this.registers.set(instruction.x, Vx | Vy);
if (this.quirkLogic) {
this.registers.set(0xf, 0);
}
return true;
case 8XY2:
this.registers.set(instruction.x, Vx & Vy);
if (this.quirkLogic) {
this.registers.set(0xf, 0);
}
return true;
case 8XY3:
this.registers.set(instruction.x, Vx ^ Vy);
if (this.quirkLogic) {
this.registers.set(0xf, 0);
}
return true;
- V-Blank Quirks (pseudocode; see actual implementation)
processFrame() {
...
for (let i = 0; i < this.instructionsPerFrame; i++) {
let instruction = this.cpu.fetch();
if (this.cpu.quirkVBlank && instruction.type == 0xD) {
break;
}
this.cpu.programCounter += 2;
this.cpu.execute(this.cpu.decode(instruction));
}
...
}
Cross-Platform
- Smooth performance on a wide range of devices, including mobile devices, tablets, and desktops.
- Virtual on-screen keypad with multi-touch support for mobile/touchscreen devices, in addition to physical keyboard input mapping.
Service Worker and PWA
- Service worker for caching, allowing the emulator to run without an internet connection.
- Support for installing the emulator as a Progressive Web App (PWA), providing a native app-like experience on supported devices.
View code on GitHub or view the live demo chip8.mpreetsingh.com.