Video summary
Gameboy Emulator Development - Part 02
Main summary
Key takeaways
Goal for this part
Move from cartridge loading/parsing to CPU emulation scaffolding—specifically setting up:
- The memory bus
- ROM access
- The instruction fetch pipeline (instruction decode + operand fetch)
Execution is still mostly a placeholder for now.
Bus + memory access setup
- Introduces the system bus as the only path for reading/writing memory-mapped data.
- Adds a development placeholder:
- If a memory region/peripheral isn’t implemented yet, the emulator prints
"not yet implemented"tostderrand exits with a negative error code.
- If a memory region/peripheral isn’t implemented yet, the emulator prints
- Current bus behavior (ROM-only for now):
- If
address < 0x8000→ read from cartridge ROM (cart read) - Writes still route through cartridge (
cart write) because some cartridges may react to writes.
- If
Bus rule of thumb: bus mediates access; cartridge decides what ROM write/react behavior is possible.
Cartridge interface changes (ROM-only for now)
- Adds cartridge methods:
cart readcart write
- Assumes ROM-only (no bank switching yet):
- Reads directly from an in-memory ROM byte array.
- Writes are not treated as normal RAM writes at this stage, but still exist because cartridge hardware can react to writes.
CPU state / registers representation
- Creates CPU register structures:
- 8-bit registers:
AF, BC, DE, HLbroken down into component registers (A/F,B/C,D/E,H/Lvia fields like A, F, B, C, etc.) - 16-bit registers:
SP(stack pointer)PC(program counter)
- 8-bit registers:
- Adds a CPU context struct to store emulator state, including:
- current fetch data
- memory destination info and whether writing to memory
- current opcode
- CPU flags such as halted and stepping mode
- an emulated cycles counter placeholder for later CPU/PPU/timer synchronization
Instruction system modeling (types + addressing modes)
- Defines an instruction abstraction with fields such as:
type(e.g., no-op, load, increment, decrement, jump, etc.)mode(addressing mode) describing how operands are fetchedreg1,reg2(optional register operands)condition(optional for conditional ops likeJP NZ,CALL NC, etc.)- additional parameters for special cases (not fully implemented yet)
- Conceptual walk-through of example opcodes and their effects on:
- program counter movement (instruction length)
- register changes
- flag behavior (e.g., Z/H/C for XOR, LD, DEC-related ops)
Example instruction encoding behavior
0x00NOP- implied addressing, does nothing
0xC3JP a16- uses a 16-bit absolute address: opcode + two operand bytes
XOR A(example viaAF)- affects flags: sets Z, clears others
LD C, d8(opcode0x0E d8)- loads immediate byte into
C
- loads immediate byte into
DEC B(example)- can set Z/H
- leaves carry unchanged (demonstrates overflow/roll behavior nuances)
Instruction table + decoder
- Creates an instruction definition table (array sized around
0x100) mapping opcodes to instruction metadata. - Initially fills only a few entries (e.g., NOP, JP, XOR-ish,
LD C,d8,DEC B). - Adds a decoder function to retrieve instruction metadata by opcode:
- If the table entry is unknown (e.g.,
type == none), it treats it as an error.
- If the table entry is unknown (e.g.,
CPU step flow (fetch + operand fetch, execution stub)
Conceptual CPU step loop:
- Fetch instruction opcode from the bus at
PC, then incrementPC - Fetch operand data based on the addressing mode
- Execute (currently mostly placeholder / “not executing yet”)
Operand fetching logic (implemented subset)
implied- no operand fetch
register- reads operand from a register (planned/partially stubbed)
- immediate 8-bit (
d8)- maps to a register
- immediate 16-bit (
d16)- reads low byte then high byte from
PC - assembles into a 16-bit value using shifts
- increments
PCaccordingly
- reads low byte then high byte from
CPU utility for register access (byte/word mixing)
- Adds
cputil.cwith helper functions:- read registers based on a register type enum (8-bit vs 16-bit)
- a “reverse” function described as combining/splitting bytes to reconstruct larger values from high/low parts
- Supports returning correct values for pairs like
AF/BC/DE/HLbased on individual bytes.
Debugging / early correctness checks
- Early runs show unknown instruction errors because not all opcodes are implemented in the instruction table (e.g., opcodes like
0x3C,0xCEappear). - Adds debug logging inside execution (printing opcode and
PC) to confirm the CPU is fetching correctly. - Fix: sets
ctx.regs.pc = 0x100at CPU init to match the Game Boy ROM entry point used in the example program. - After adjustments:
PCincrements properly- the emulator reads expected opcodes (e.g., reads NOP then
0xC3forJP)
- Undefined opcodes are expected to fail at this stage—the emphasis is on getting fetch/decode working first.
Main speakers / sources
- Creator / speaker: Low-level developer on the “low level dev” YouTube channel
- Gameboy Emulator Development - Part 02