⚠️ Clean-Room / Educational

This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.

Default Joypad and Paddle

Date:2025-12-17 StepID:0031 State: Verified

Summary

It was implementedjoypad(button and direction control) of the Game Boy with Active Low logic, and theBGP palette initializationto 0xE4 by default. Diagnostic logs revealed that the game was polling the Joypad (P1) and that the paddle was at 0x00 (all white), making graphics invisible even if rendered correctly. With these corrections, The emulator can respond to button presses and display correctly colored graphics.

Hardware Concept

Hejoypadof the Game Boy uses a system ofActive Lowwhere:

  • 0 = Button pressed
  • 1 = Button released

Register P1 (0xFF00) is read/write:

  • WRITE (bits 4-5):The game selects what to read:
    • Bit 4 = 0: Want to read Addresses (Right, Left, Up, Down)
    • Bit 5 = 0: Want to read Buttons (A, B, Select, Start)
  • READ (bits 0-3):The game reads the state according to the selector:
    • Bit 0: Right/A
    • Bit 1: Left/B
    • Bit 2: Up/Select
    • Bit 3: Down/Start

When a button goes from Released (1) to Pressed (0), the Joypad interrupt is activated (Bit 4 in IF, 0xFF0F).

TheBGP palette(Background Palette, 0xFF47) controls the background colors. On a real Game Boy, The Boot ROM leaves this registry set to0xE4(11100100 in binary), which maps:

  • Index 0 → White (0)
  • Index 1 → Light gray (1)
  • Index 2 → Dark gray (2)
  • Index 3 → Black (3)

If BGP remains at 0x00 (all white), even if the game renders the tiles correctly, all pixels whites will appear and the screen will appear completely white.

Fountain:Pan Docs - Joypad Input, Background Palette Register (BGP)

Implementation

A class was createdjoypadwhich implements the Active Low logic and manages the P1 register. Integrated into the MMU to intercept P1 reads/writes, and into Viboy to capture events of pygame keyboard and update the Joypad status.

Components created/modified

  • src/io/joypad.py: New classjoypadwith methodspress(), release(), read(), write(). Implements Active Low logic and requests interrupts when a button is pressed.
  • src/io/__init__.py: New I/O module.
  • src/memory/mmu.py:
    • BGP initialization to 0xE4 on__init__().
    • P1 read/write intercept (0xFF00) delegating to Joypad.
    • Methodset_joypad()to connect the Joypad to the MMU.
  • src/viboy.py:
    • Joypad instantiation and connection to the MMU.
    • Method_handle_pygame_events()which captures keyboard events and updates the Joypad.
    • Key Mapping: K_UP/DOWN/LEFT/RIGHT (directions), K_z (A), K_x (B), K_RETURN (Start), K_RSHIFT (Select).
  • tests/test_io_joypad.py: Complete test suite (14 tests) validating initialization, Active Low logic, read selector, interrupts and integration with MMU.

Design decisions

Active Low Logic:Correctly implemented where True = pressed (bit 0 in hardware), False = released (bit 1 in hardware). When reading P1, if a button is pressed, the corresponding bit is cleared.

Transition detection:Interrupt is only requested when a button passes release to pressed. If calledpress()several times withoutrelease()intermediate, only the first Pressing activates the interruption.

Reading selector:The selector (bits 4-5) is saved when the game writes to P1. When reading, the state of the corresponding buttons is returned according to the selector.

Event management:Centralized pygame event handling in_handle_pygame_events()within Viboy, rather than in the Renderer, to maintain separation of responsibilities.

Affected Files

  • src/io/joypad.py- New Joypad class
  • src/io/__init__.py- New I/O module
  • src/memory/mmu.py- BGP initialization=0xE4, P1 interception, set_joypad() method
  • src/viboy.py- Joypad instantiation, _handle_pygame_events() method
  • tests/test_io_joypad.py- Complete test suite (14 tests)

Tests and Verification

Unit tests were executed with pytest validating:

Tests executed

  • Command: python3 -m pytest tests/test_io_joypad.py -v
  • Around:macOS, Python 3.9.6
  • Result:14 tests PASSED in 0.33s

Tests implemented

  • test_default_palette_init: Verifies that BGP is initialized to 0xE4.
  • test_joypad_initial_state: Verify that all buttons are released at startup.
  • test_joypad_read_default: Verify reading with default selector.
  • test_joypad_read_directions: Verifies reading of addresses when they are released.
  • test_joypad_read_directions_pressed: Verifies reading when Right is pressed (bit 0 = 0).
  • test_joypad_read_buttons: Verifies reading of buttons when they are released.
  • test_joypad_read_buttons_pressed: Verifies reading when A is pressed (bit 0 = 0).
  • test_joypad_press_interrupt: Verifies that pressing a button activates IF bit 4.
  • test_joypad_release_no_interrupt: Verify that releasing a button does NOT activate interrupt.
  • test_joypad_press_twice_no_double_interrupt: Verify that pressing twice and holding only activates interruption the first time.
  • test_joypad_press_release_press_interrupt: Verify that press-release-press triggers interrupt both times.
  • test_joypad_mmu_integration: Verify integration with MMU (read/write P1 works correctly).
  • test_joypad_all_directions: Verifies reading of all addresses when they are pressed.
  • test_joypad_all_buttons: Verifies reading of all buttons when they are pressed.

Test code (example: interrupt detection)

def test_joypad_press_interrupt(self) -> None:
    """Test: Pressing a button should activate the Joypad interrupt (bit 4 in IF)"""
    mmu = MMU(None)
    joypad = Joypad(mmu)
    
    # Clear IF
    mmu.write_byte(IO_IF, 0x00)
    assert(mmu.read_byte(IO_IF) & 0x10) == 0
    
    # Press Start
    joypad.press("start")
    
    # Verify that IF bit 4 is active
    if_val = mmu.read_byte(IO_IF)
    assert (if_val & 0x10) != 0

What is valid:This test shows that the Joypad hardware requests an interrupt when a button is pressed, activating bit 4 of the IF register (Interrupt Flag). This allows the CPU respond to user input events.

Sources consulted

Educational Integrity

What I Understand Now

  • Active Low is essential:The reverse logic (0 = pressed, 1 = released) is a feature of the actual Game Boy hardware, not an arbitrary choice. This allows the hardware to use pull-up resistors and detect pulses by connecting to ground.
  • Reading selector:The fact that the game has to write to P1 to select what to read (addresses vs buttons) is a hardware limitation that reduces the number of pins needed. The game must alternate between reading directions and buttons.
  • Transition interruptions:The interrupt is only activated on the transition from released to pressed, not while the button is held down. This prevents interruption spam and allows the game to detect discrete "clicks."
  • Importance of default values:Although we haven't implemented Boot ROM yet, the values ​​left by Boot ROM (such as BGP=0xE4) are critical. Without these values, many games do not work correctly because they assume the Boot ROM set them up.

What remains to be confirmed

  • Interrupt Timing:It's not entirely clear if there is any delay between pressing a button and the IF bit setting, or if it is instantaneous. For now, we assume it's instantaneous.
  • Behavior of bits 4-5 when reading:On real hardware, when P1 is read, bits 4-5 reflect the selector (what the game wrote), but there could be behavioral details that are not fully documented. For now, we keep bits 4-5 as they are in the selector.

Hypotheses and Assumptions

The interrupt request is assumed to be instantaneous when a button is pressed (no delay). It is assumed that if the game has not written a valid selector (both bits 4-5 = 1), the read returns all bits set to 1 (all buttons "released" from a hardware perspective).

Next Steps

  • [ ] Test the emulator with a real game to verify that the Joypad works correctly
  • [ ] Implement Scroll (SCX/SCY) so that the game can scroll the background
  • [ ] Implement Window (WX/WY) so that the game can display an overlay window
  • [ ] Implement Sprites (OAM) so that the game can display moving objects
  • [ ] Implement full Timer (TIMA, TMA, TAC) for precise synchronization