This document focuses on the current CHIP-8 emulation core implemented in src/cpu.rs.
The emulator models the standard CHIP-8 machine state:
V0..VF: 16 general-purpose 8-bit registersI: 16-bit index registerPC: program counterSP: stack pointerstack[16]: call stackdelay_timer: decremented at 60 Hzsound_timer: decremented at 60 Hzmemory[4096]: full CHIP-8 memory space
Current layout used by Oxide:
0x000-0x04F: reserved0x050-0x09F: built-in fontset (glyphs0-F)0x0A0-0x1FF: reserved / interpreter space0x200-0xFFF: loaded ROM program area
ROM loading starts at the standard CHIP-8 start address.
The CPU cycle follows the standard pattern:
- Fetch 2 bytes at
PC - Increment
PC - Decode opcode fields
- Execute the corresponding instruction
- Mutate CPU, display, keypad, or memory state
The main frame loop decides how many CPU cycles run per frame based on cycles_par_seconde and accumulated frame time.
Oxide implements the full CHIP-8 opcode set currently targeted by the project, including:
- screen control and return
- jumps and calls
- conditional skips
- register load / immediate add
- register ALU operations
- index register operations
- random masked values
- sprite drawing
- keypad-dependent skips
- timer operations
- BCD conversion
- register-memory transfer operations
Sprites are drawn through XOR semantics on the logical 64x32 framebuffer.
During DRW:
- bits are read from memory at
I - pixels are XORed into
Display VFis used as the collision flag
Depending on the selected quirk preset, draw behavior can wrap or clip at the display edges.
delay_timer and sound_timer are not tied to raw CPU cycle count.
Instead:
- the app accumulates time separately
- timers are ticked at approximately
60 Hz sound_timer > 0drives the audio buzzer
This keeps timers stable even if CPU speed changes.
Oxide exposes 5 configurable CHIP-8 quirks through CpuQuirks:
shift_uses_vyjump_uses_vxdraw_clipsload_store_increment_ilogic_clears_vf
Available presets:
CHIP-8CHIP-48SUPER-CHIPCustom
Preset mapping is defined in src/types.rs.
The CPU reads key state from Keypad.
Keypad itself is populated from a merged input pipeline:
- keyboard
- mouse button bindings
- gamepad state
- debug terminal virtual keypad state
This means the CPU always consumes a single normalized keypad view.
When a ROM is loaded:
- CPU memory is reset appropriately
- the ROM is copied into program memory
- display and runtime accumulators are reset as needed
- save states associated with the ROM are reloaded from disk if available
A stop/reset action clears active runtime state without wiping persisted user settings.
The CPU itself does not own audio playback.
Instead:
sound_timeris decremented in the runtime loopapp.rschecks whether sound should be activeAudioEnginestarts or stops the buzzer accordingly
The emulator exposes a test-report path for diagnostics.
emit_test_report() can log:
- ROM name
- running/paused state
- current
PCand opcode - register dump
- display activity summary
- keypad state
- active quirk preset and flags
This is especially useful when validating ROM behavior or quirk regressions.