This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Implementation of CPU Registers (LR35902)
Summary
The class was implementedRegisterswhich manages all the registers of the Game Boy's LR35902 CPU.
Included are 8-bit registers (A, B, C, D, E, H, L, F), 16-bit registers (PC, SP), and 16-bit virtual pairs (AF, BC, DE, HL).
The hardware peculiarity of the F register (low bits always 0) and complete helpers for flag management were implemented.
Created a complete unit test suite with 15 tests, all passing correctly.
Hardware Concept
The Game Boy uses a hybrid CPU based on Z80/8080 architecture calledLR35902. The main feature is that it has 8-bit registers that can be combined into virtual 16-bit pairs. for addressing and arithmetic operations.
8 bit registers
- A (Accumulator):Main register for arithmetic and logical operations
- B, C, D, E, H, L:General Purpose Registers
- F (Flags):Status register with hardware peculiarity: the lower 4 bits are always 0
16-bit virtual peers
These are not separate physical registers, but logical combinations of 8-bit registers:
- AF:A (high byte) + F (low byte, but only bits 4-7 valid)
- BC:B (high byte) + C (low byte) - frequently used for counters and addresses
- OF:D (high byte) + E (low byte) - used for addresses and data
- HL:H (high byte) + L (low byte) - widely used for indirect addressing
16 bit registers
- PC (Program Counter):Program counter, points to the next instruction to execute
- SP (Stack Pointer):Stack pointer for subroutine calls and interrupt handling
F register flags
The F register stores the state of the CPU using flags in the high bits:
- Bit 7 (Z - Zero):Triggered when the result of an operation is zero
- Bit 6 (N - Subtract):Indicates if the last operation was a subtraction
- Bit 5 (H - Half Carry):Indicates carry from bit 3 to 4 (low nibble)
- Bit 4 (C - Carry):Indicates carry of bit 7 (overflow in addition or borrow in subtraction)
Important hardware peculiarity:The low 4 bits (0-3) of the F register are always 0 in real hardware. This is not a software convention, but a physical characteristic of the chip.
Implementation
The class was implementedRegistersin Python with strict typing and complete educational documentation.
All operations ensure correct wrap-around using bitwise masks.
Components created/modified
- Registers Class:Manage all CPU registers with individual getters/setters methods
- Methods for virtual pairs:get_af, set_af, get_bc, set_bc, get_de, set_de, get_hl, set_hl
- Helpers for flags:set_flag, clear_flag, check_flag, and individual methods (get_flag_z, etc.)
- Flag constants:FLAG_Z, FLAG_N, FLAG_H, FLAG_C with hexadecimal values
Design decisions
- Explicit wrap-around:All write operations apply masks (
& 0xFFfor 8 bits,& 0xFFFFfor 16 bits) to simulate hardware behavior - F register mask:Applies
REGISTER_F_MASK = 0xF0on all writes to F to ensure low bits are always 0 - Bitwise operations for pairs:The 16-bit pairs are combined using
(high_byte<< 8) | byte_bajoand they separate with(value >> 8) & 0xFFandvalue & 0xFF - Individual methods per record:Individual getters/setters are provided for ease of future use in implementing opcodes
Package structure
files were created__init__.pyinsrc/cpu/andtests/to convert the folders into valid Python packages.
Affected Files
src/cpu/__init__.py- CPU module initialization (new)src/cpu/registers.py- Registers class with all registers and flags (new, 361 lines)tests/__init__.py- Initialization of the test module (new)tests/test_registers.py- Complete unit test suite (new, 321 lines)COMPLETE_REPORT.md- Log update (modified)
Tests and Verification
A complete suite of unit tests was implemented withpytest:
- Test 1 - Wrap-around in 8-bit registers:Verify that values > 255 do wrap-around correctly (256 → 0, negative values convert correctly)
- Test 2 - 16-bit pairs:Verifies that reading/writing virtual pairs (BC, DE, HL, AF) correctly modifies individual registers and vice versa
- Test 3 - F register mask:Verify that the F register ignores the 4 low bits (0xFF → 0xF0, 0x0F → 0x00)
- Test 4 - Flag Helpers:Check set_flag, clear_flag, check_flag and individual methods (get_flag_z, get_flag_n, get_flag_h, get_flag_c)
- Additional tests:PC, SP, default initialization
Result:15 tests in total, all passing correctly (0.38s).
It was verified that there are no linting errors in the created files.
Sources consulted
- Bread Docs:Game Boy CPU Manual - Registers and Flags Overview
- General knowledge of architecture:Register concepts in 8-bit CPUs and 16-bit virtual pairs
Note: The implementation was based on general knowledge of CPU architecture and the general specifications of the LR35902. The F register quirk (low bits always 0) is a known feature of the actual Game Boy hardware.
Educational Integrity
What I Understand Now
- Virtual pairs:16-bit registers like BC are not separate physical registers, but rather logical combinations of 8-bit registers using bitwise operations. The combination is done with
(high_byte<< 8) | byte_bajoand the separation with shift right and masks. - F register mask:The actual Game Boy hardware physically forces the low bits from F to 0. This is not a software convention, but rather a limitation of the chip. That's why we apply the 0xF0 mask on all writes.
- Wrap-around:It is critical to correctly simulate hardware behavior. Values that exceed the valid range must be wrapped around using modulo operations implemented with bitwise masks.
- Flags as individual bits:Each flag is a specific bit in the F register. They can be activated/deactivated using OR and AND operations with specific masks (FLAG_Z = 0x80, FLAG_C = 0x10, etc.).
What remains to be confirmed
- Initial register values:The exact values of the registers at the beginning of the Game Boy boot. Pending verification with documentation or tests of permitted ROMs.
- Behavior of flags in complex operations:The specific behavior of the flags (especially H and C) in complex arithmetic operations will be implemented when the ALU (Arithmetic Logic Unit) is created.
- Initialization order:If there is any specific order or particular initial state that the registers must be in when starting the emulator.
Hypotheses and Assumptions
The behavior of wrap-around using bitwise masks is assumed to be correct for all cases. This is a standard assumption in emulation, but will be validated with more complex tests when actual arithmetic operations are implemented.
The implementation of virtual pairs using bitwise operations is assumed to be correct. This is consistent with how real CPUs work, but will be fully validated when opcodes using these pairs are implemented.
Next Steps
- [ ] Implement Memory Management Unit (MMU) for address mapping
- [ ] Implement ROM loading system
- [ ] Implement RAM and VRAM memory
- [ ] Implement basic I/O registers
- [ ] Start implementation of the ALU for basic arithmetic operations
- [ ] Implement basic opcode decoding (start with simple opcodes like NOP)