Back to project journal

PROJECT NOTES / C++20

NES Emulator

A desktop NES emulator built around cycle-aware CPU and PPU behavior, cartridge mapping, synchronized rendering, and a live debugging interface.

NES emulator desktop window showing the ImGui CPU debugger beside the rendered game output
Hardware-synchronized emulation: Dear ImGui debugging dashboard running side by side with modern OpenGL 60 FPS game output.
Modern C++20
Cycle-Accurate 6502 CPU
256 × 240 PPU (60 FPS)
4 Cartridge Mappers
5-Channel APU (44.1 kHz)
Dear ImGui & OpenGL 3.3
Interactive C++20 NES Emulator & ImGui Live Debugger
Cycles: 7Frame: #1
6502 CPU RegistersMOS 6502 @ 1.79 MHz
A (ACC)
$00
X (IDX)
$00
Y (IDX)
$00
SP (STACK)
$FD
PC (PROG)
$C000
STATUS FLAGS REGISTER (P):%00100110
N
0
V
0
-
1
B
0
D
0
I
1
Z
1
C
0
2C02 PPU Raster StatusRicoh 2C02 @ 5.37 MHz
Scanline:
0 / 261
PPU Cycle:
21 / 340
VBlank ($2002.7):INACTIVE
Sprite 0 Hit ($2002.6):FALSE
Nametable Mirroring:Vertical (Horiz Scroll)
Active Sub-Palettes ($3F00)
6502 Disassembler StreamPC Target: $C000
▶$C000SEI
; Disable maskable IRQ interrupts
$C001CLD
; Clear decimal mode flag (NES 2A03 lacks BCD)
$C002LDX #$FF
; Initialize stack pointer index to $FF
$C004TXS
; Transfer X to Stack Pointer (SP = $FF)
$C005LDA #$00
; Clear accumulator (A = $00)
$C007STA $2000
; Write to PPUCTRL: disable NMI on VBlank
$C00ASTA $2001
; Write to PPUMASK: disable screen rendering
$C00DLDX #$0A
; Load X register with loop counter = 10
$C00FSTA $00,X
; Zero-page store indexed: RAM[$00 + X] = $00
$C011BIT $2002
; Poll PPUSTATUS: wait for VBlank and clear flag
$C014LDA #$80
; Load A with $80 (bit 7 = 1: enable NMI)
$C016STA $2000
; Write PPUCTRL: enable NMI on VBlank!
Zero-Page RAM ($0000 - $000F)2KB Internal RAM
$00
00
$01
00
$02
00
$03
00
$04
00
$05
00
$06
00
$07
00
$08
00
$09
00
$0A
00
$0B
00
$0C
00
$0D
00
$0E
00
$0F
00
APU Audio Output:44.1 kHz (SDL2)
Active Mapper:MMC1 (Mapper 1)
Debugger attached via OpenGL / Dear ImGui docking bus
OpenGL 3.3 Core•SDL2 Audio Queue•C++20

SYSTEMS ARCHITECTURE / C++20 EMULATION

Cycle-Aware Console Emulation & Real-Time Debugging in Modern C++20

The Nintendo Entertainment System (NES) is a masterclass in synchronous hardware design. Operating with a 1.789 MHz Ricoh 2A03 CPU, a 5.37 MHz Ricoh 2C02 Picture Processing Unit, and an integrated Audio Processing Unit, the system requires cycle-exact timing coordination. This desktop emulator implements the entire hardware path in modern C++20, coupling cycle-accurate execution with a dockable Dear ImGui debugging suite that exposes internal registers, disassembly, memory maps, and raster beam timing in real time.

01

Ricoh 2A03 (MOS 6502) CPU Core

Executes all 56 official 6502 opcodes plus documented unofficial/illegal instructions. Models addressing modes (Zero Page, Indexed, Indirect, Absolute), cycle penalty delays for page-boundary crossings, and full interrupt logic (RESET at $FFFC, NMI at $FFFA, IRQ/BRK at $FFFE). BCD decimal mode is explicitly disabled in hardware matching Ricoh silicon.

02

Ricoh 2C02 Picture Processing Unit (PPU)

Renders at 256 × 240 resolution @ 60 FPS over 262 scanlines. Implements the complete 'Loopy' scrolling engine (registers v, t, x, w), 2 Pattern Tables ($0000/$1000), 4 Nametables with dynamic mirroring (Horizontal, Vertical, Single-Screen, Four-Screen), 64-sprite OAM evaluation with 8-sprites-per-scanline limits, and precise cycle-level Sprite 0 hit detection.

03

Ricoh 2A03 Audio Processing Unit (APU)

Synthesizes 5 independent audio channels: Pulse 1 & Pulse 2 (variable duty cycles, sweep units, envelope decay), Triangle (32-step pseudo-sine with linear counter), Noise (15-bit LFSR pseudo-random generator), and DMC (Delta Modulation for 1-bit samples). Features 4-step and 5-step frame sequencers mixed via non-linear formulas into SDL2 audio streams at 44.1 kHz.

04

Cartridge Mappers & Bus Multiplexing

Parses iNES header formats (.nes) and multiplexes cartridge memory spaces. Supports Mapper 0 (NROM: 16KB/32KB PRG, 8KB CHR), Mapper 1 (MMC1: 5-bit serial shift register with bank switching and dynamic mirroring), Mapper 2 (UxROM: PRG bank switching with fixed upper bank), and Mapper 3 (CNROM: 8KB CHR banking).

Modern C++20 Architectural Highlights

Compile-Time Opcode Table

Utilizes constexpr lookup tables for O(1) branchless instruction decoding and cycle costing.

Safe Memory Windows

Employs std::span to view and slice memory buffers without allocations or pointer decay.

Bit Manipulation (<bit>)

Leverages standard library bit operations (std::rotl, std::rotr, std::popcount) for fast shift and flag evaluations.

Memory Ownership

Encapsulates Cartridge, Bus, PPU, and APU peripherals within std::unique_ptr with zero raw pointer leaks.

Cache-Friendly Flat Arrays

Stores 2KB internal RAM and 2KB VRAM in contiguous std::array buffers for optimal L1 data cache locality.

NES CPU Memory Bus Architecture ($0000 - $FFFF)

$0000 - $07FF
2KB Internal Work RAM (Zero Page at $0000-$00FF, Stack at $0100-$01FF)
$0800 - $1FFF
Mirrors of $0000 - $07FF (mirrored 3 times)
$2000 - $2007
PPU I/O Registers (PPUCTRL, PPUMASK, PPUSTATUS, OAMADDR, OAMDATA, PPUSCROLL, PPUADDR, PPUDATA)
$2008 - $3FFF
Mirrors of $2000 - $2007 (mirrored every 8 bytes)
$4000 - $4017
APU Sound Registers, OAM DMA Register ($4014), Joypad 1 & 2 ($4016, $4017)
$4020 - $5FFF
Cartridge Expansion ROM
$6000 - $7FFF
Cartridge Battery-Backed PRG-RAM (Save RAM)
$8000 - $BFFF
Cartridge PRG-ROM (Low Bank / Switchable via Mapper)
$C000 - $FFFF
Cartridge PRG-ROM (High Bank / Fixed / Vectors: NMI @ $FFFA, RESET @ $FFFC, IRQ @ $FFFE)

Gamepad Controls & Emulator Shortcuts

Arrow Keys
NES D-Pad (Up, Down, Left, Right)
Z / X
NES A Button / B Button
Enter / Right Shift
NES Start / Select
F1
Toggle Dear ImGui Debugger Docking Windows
F2
Single-Step 6502 CPU Instruction
F3
Step PPU Frame (Advance 262 Scanlines)
F5 / Space
Toggle Run / Pause Execution
Esc
Exit Emulator

Build & Toolchain Environment

Modern C++20 compiler (MSVC 2022 / Clang / GCC 11+) · CMake 3.20+ · Ninja / MSBuild · Modern OpenGL 3.3+ · SDL2 · Dear ImGui (Docking Branch)

Explore Desktop NES Emulator on GitHub