r/EmuDev • u/Imaginary_Heat_2235 • 3h ago
NES I put the NES inside a CUDA kernel, one console per thread, and ran 65k of them at once
Built this for a reinforcement learning project (PPO on Super Mario Bros), but the emulator side turned out to be the interesting part, so I figured this sub would care more about that.
One CUDA thread runs one entire console. 6502, PPU, bus, OAM DMA. State is structure-of-arrays indexed by env id, and CPU registers stay in hardware registers for the life of the kernel. 65,536 independent consoles at about 3.3M steps/s on an A100.
- CPU passes the Klaus functional test suite, cycle counts included
- PPU is not cycle accurate. SMB's title screen never advances past the menu because of a timing bug I haven't fixed. I sidestep it by resetting from a save state
- No APU. The registers exist as a byte array, nothing generates sound
- Mapper 0 only
Two results that surprised me:
Opcode-binned dispatch (sorting a warp's threads by opcode before executing) came out 3.2x slower than just letting them diverge. I tested it on instruction streams sampled from 535M profiled SMB executions. A decorrelated warp holds about 20.6 distinct opcodes, and hardware SIMT reconvergence was already sharing fetch and addressing acrossthem. Explicit binning serializes what it was already sharing.
Putting the zero page in shared memory was a 1.9x regression. 256 bytes across 128 threads is 32KB per block, which drops occupancy from 32 warps to 6.
https://github.com/hbofz/NeSLE
Would love help with mappers. MMC1 and MMC3 would open up most of the library.