Skip to content

kabukki/wasm-nes

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

97 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ•Ή Nintendo Entertainment System

A NES emulator written in Rust compiled to WebAssemly for usage on the web.

The Nintendo Entertainment System (NES) is an 8-bit third-generation home video game console produced by Nintendo. Nintendo first released it in Japan as the Family Computer, commonly known as the Famicom, in 1983. The NES, a remodelled version, was released internationally in the following years.

Overview

  • βœ… CPU: all official opcodes # Central Processing Unit (Ricoh 2A03)
  • βœ… PPU: Pixel Processing Unit
  • 🚧 APU: Audio Processing Unit: Pulse, triangle, noise, DMC.
  • βœ… Input: Controller input
  • βœ… Mappers: NROM, MMC1, UxROM, 003, CNROM, AxROM, GxROM.
  • βœ… Save states: game saves via cartridge RAM

Timing

The emulator synchronizes to video with the requestAnimationFrame function, which usually matches the refresh rate of the display.

  • CPU runs at 500Hz
  • Timers run at 60Hz

At every repaint, enough emulator cycles are run to simulate that the duration for one frame has passed. Given an ideal refresh rate of 60FPS, that is 1/60s.

Known limitations

The emulator currently lacks in the following areas:

  • Open bus behaviour is missing
  • Precise PPU timing
  • Some sprites are not displayed correctly

Usage

Before creating the emulator, you need to call the init function which will correctly instantiate and setup the WebAssembly module.

import init, { Emulator } from '@kabukki/wasm-nes';

init().then(() => {
    const emulator = new Emulator();

    document.getElementById('input').addEventListener('change', async (e) => {
        const buffer = await e.target.files[0]?.arrayBuffer();
        emulator.load(new Uint8Array(buffer));
        emulator.start();
    });
}).catch(console.error);

useDebug

This hook provides various information regarding emulation status.

  • logs emulator logs (nestest compliant) produced through Rust's log facade.
  • emulator emulator state
  • performance measures of browser frame performance

Tests

Emulation accuracy is tested thanks to test ROMs taken from https://wiki.nesdev.com/w/index.php/Emulator_tests (available here), and inspired from http://tasvideos.org/EmulatorResources/NESAccuracyTests.html. Here is the summary of results, you can find details below.

Component Passed Total %
CPU 21 30 70%
PPU 10 41 24%
APU 3 18 17%
Mappers - - -
Total 34 89 38%

CPU

Test Status
branch_timing_tests/branch_basics βœ… Passed
branch_timing_tests/backward_branch βœ… Passed
branch_timing_tests/forward_branch βœ… Passed
cpu_dummy_reads ❌ Failed
cpu_dummy_writes/cpu_dummy_writes_oam ❌ Failed
cpu_dummy_writes/cpu_dummy_writes_ppumem ❌ Failed
cpu_exec_space/test_cpu_exec_space_apu ❌ Failed
cpu_exec_space/test_cpu_exec_space_ppuio ❌ Failed
cpu_interrupts_v2 ❌ Failed
cpu_reset/ram_after_reset βœ… Passed
cpu_reset/registers βœ… Passed
cpu_timing_test6 βœ… Passed
instr_misc ❌ Failed
instr_test_v5/basics βœ… Passed
instr_test_v5/implied βœ… Passed
instr_test_v5/immediate ⚠️ Official only
instr_test_v5/zero_page ⚠️ Official only
instr_test_v5/zp_xy ⚠️ Official only
instr_test_v5/absolute ⚠️ Official only
instr_test_v5/abs_xy ⚠️ Official only
instr_test_v5/ind_x ⚠️ Official only
instr_test_v5/ind_y ⚠️ Official only
instr_test_v5/branches βœ… Passed
instr_test_v5/stack βœ… Passed
instr_test_v5/jmp_jsr βœ… Passed
instr_test_v5/rts βœ… Passed
instr_test_v5/rti βœ… Passed
instr_test_v5/brk ❌ Failed
instr_test_v5/special ❌ Failed
nestest ⚠️ Official only

PPU

Test Status
blargg_ppu_tests_2005.09.15b/palette_ram βœ… Passed
blargg_ppu_tests_2005.09.15b/sprite_ram βœ… Passed
blargg_ppu_tests_2005.09.15b/vbl_clear_time ❌ Failed
blargg_ppu_tests_2005.09.15b/vram_access ❌ Failed
nmi_sync/demo_ntsc ❌ Failed
oam_read βœ… Passed
oam_stress ❌ Failed
oamtest3 ❌ Failed
ppu_open_bus ❌ Decay not implemented
ppu_read_buffer ❌ Failed
ppu_sprite_hit/basics βœ… Passed
ppu_sprite_hit/alignment ❌ Failed
ppu_sprite_hit/corners ❌ Failed
ppu_sprite_hit/flip ❌ Failed
ppu_sprite_hit/left_clip ❌ Failed
ppu_sprite_hit/right_edge ❌ Failed
ppu_sprite_hit/screen_bottom ❌ Failed
ppu_sprite_hit/double_height βœ… Passed
ppu_sprite_hit/timing ❌ Failed
ppu_sprite_hit/timing_order ❌ Failed
ppu_sprite_overflow/basics ❌ Failed
ppu_sprite_overflow/details βœ… Passed
ppu_sprite_overflow/timing ❌ Failed
ppu_sprite_overflow/obscure ❌ Failed
ppu_sprite_overflow/emulator ❌ Failed
ppu_vbl_nmi/vbl_basics βœ… Passed
ppu_vbl_nmi/vbl_set_time ❌ Failed
ppu_vbl_nmi/vbl_clear_time βœ… Passed
ppu_vbl_nmi/nmi_control ❌ Failed
ppu_vbl_nmi/nmi_timing ❌ Failed
ppu_vbl_nmi/suppression ❌ Failed
ppu_vbl_nmi/nmi_on_timing ❌ Failed
ppu_vbl_nmi/nmi_off_timing ❌ Failed
ppu_vbl_nmi/even_odd_frames βœ… Passed
ppu_vbl_nmi/even_odd_timing ❌ Failed
sprdma_and_dmc_dma -
sprite_overflow_tests/basics ❌ Failed
sprite_overflow_tests/details βœ… Passed
sprite_overflow_tests/timing ❌ Failed
sprite_overflow_tests/obscure ❌ Failed
sprite_overflow_tests/emulator ❌ Failed

APU

Test Status
apu_mixer/dmc ❌ Failed
apu_mixer/noise ❌ Failed
apu_mixer/square ❌ Failed
apu_mixer/triangle ❌ Failed
apu_reset/4015_cleared ❌ Failed
apu_reset/4017_timing ❌ Failed
apu_reset/4017_written βœ… Passed
apu_reset/irq_flag_cleared βœ… Passed
apu_reset/len_ctrs_enabled ❌ Failed
apu_reset/works_immediately ❌ Failed
apu_test/len_ctr ❌ Failed
apu_test/len_table ❌ Failed
apu_test/irq_flag βœ… Passed
apu_test/jitter ❌ Failed
apu_test/len_timing ❌ Failed
apu_test/irq_flag_timing ❌ Failed
apu_test/dmc_basics ❌ Failed
apu_test/dmc_rates ❌ Failed
... ...

Mappers

Test Status
Holy Mapperel -
... ...

Development

Toolchain

The emulator is written in Rust and compiled into a WebAssembly module through wasm-pack and uses wasm-bindgen to ease interoperability with the JavaScript environment. A custom JavaScript file wraps the produced package for convenience when consuming it in JavaScript.

.rs ---[wasm-pack]---> .wasm <--> JS wrapper <--- JS

The emitted JS wrapper is distributed as an ES Module.

Compiling a test program

You'll need a 6502 assembler & linker such as cc65.

cl65 roms/test.s -C roms/test.cfg -o roms/test.bin

Resources

Reference

Opcodes

Examples / tutorials / inspiration

ROMs & Tests

Videos & talks

Assembly

Audio