Video summary
Gameboy Emulator Development - Part 03
Main summary
Key takeaways
CPU instruction execution architecture (C code refactor)
- Introduces a new source file:
cpu_proc.cto handle instruction processing/execution. - Updates
cpu.hto add:- A function pointer type for instruction handlers:
- Returns
void - Takes a
cpu context*parameter
- Returns
inst_get_processorto map an instruction’s type to the correct handler function.
- A function pointer type for instruction handlers:
- Implements an array of function pointers (conceptually a map/hash map, but implemented as an array) as the dispatch table:
- Default handlers are
null - Only implemented instruction handlers are assigned
- Includes a
proc_nonehandler that errors/exits when an unimplemented instruction is encountered - Adds placeholder handlers such as
proc_ld(TODO)
- Default handlers are
Jump instruction support with flag-based conditions
- Adds
proc_jpto implement jump instructions, including conditional jumps. - Uses CPU flags (from “CPU registers and flags”):
- Z (zero) flag = bit 7
- C (carry) flag = bit 4
- Implements a helper:
check_condition()based on the instruction’s condition type, including:- always
- if carry
- if not carry
- if zero
- if not zero
- When performing a jump:
- Updates the CPU program counter (PC) to the fetched data value
- Notes that jump timing must be coordinated with PPU/timer (cycle synchronization)
Instruction dispatch / runtime execution flow
In the CPU step (or equivalent execution function):
- Calls
inst_get_processor(current_instruction.type)to retrieve the handler function pointer. - If the handler is
null, the program exits (or terminates execution). - Otherwise, the handler is invoked with the CPU context.
- Fixes/handles issues around:
ctxpointer correctness- missing/undefined
inst_get_processor
Debugging improvements: logging and tracing executed instructions
- Adds a logging format that prints, per instruction:
- PC
- instruction name (via an instruction-name lookup array/map)
- opcode byte
- the next operand bytes (reads
pc+1,pc+2) for visibility - register values such as A, B, C
- Improves fetch error handling:
- Initially adds a check for
current_instruction == null(as a “hack”) - Later removes it after troubleshooting to improve error visibility
- Initially adds a check for
- Error reporting covers cases like:
- “unknown instruction/addressing mode”
- encountering an opcode such as
F3identified as DI (disable interrupts)
Incremental instruction coverage
- Adds NO-OP support via a handler that does nothing.
- Implements a default implied addressing mode:
- Undefined instructions now default to implied addressing, improving handling/logging.
- Implements DI (opcode
F3):- Adds
int master enabledto the CPU structure and sets it tofalsewhen DI executes. - Maps
F3to the DI instruction with implied addressing.
- Adds
- Runs ROM tests:
- DI appears to execute early
- Execution continues until unimplemented instructions occur (e.g., load stack pointer)
- Eventually reaches Tetris, where XOR is encountered
Implementing XOR and flag updates
- Implements
XOR A, <operand>:- Used by ROM behavior
- Opcode
AFreferenced as XOR in the discussion sample
- XOR handler logic:
A = A XOR fetched_data(masked with0xFFfor the relevant byte)
- Updates flags using a planned helper:
- Creates/uses
cpu_set_flags - Sets Z if the result is zero
- Clears/sets N/H/C appropriately (set to zeros for the XOR case)
- Creates/uses
- Fixes XOR behavior to confirm it changes register A (example:
Achanges from0x01to0x00)
Stated design rationale
The author contrasts this dispatch-table approach with a giant
switchstatement.
- Prioritizes readability and debuggability over maximum speed.
- Notes that modern compiler optimizations should still be “fast enough” for emulation.
Main speakers / sources
- Single speaker/author: the creator of the “low-level … Game Boy Emulator Development” series.
- No other distinct sources mentioned.