⚠️ 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.

Player Input: Joypad Implementation

Date:2025-12-20 StepID:0182 State: ✅ VERIFIED

Summary

The emulator has reached a stable and synchronized state, but the screen is still blank because the CPU is stuck in a final initialization loop. The diagnostic indicates that the CPU is waiting for a change in the Joypad register (P1,0xFF00) to generate a random seed (entropy) before proceeding to copy the graphics to VRAM. This Step implements Joypad registration in the C++ core and connects it to the Pygame event loop so that the user's keystrokes are communicated to the game, resolving the last initialization deadlock.

Hardware Concept: The Joypad Matrix Scan

The Game Boy Joypad is not a simple registration. It is a 2x4 matrix that the CPU must scan to read the status of the buttons. The recordP1 (0xFF00)control this process:

  • Bits 5 and 4 (Write):The CPU writes here to select which "row" of the array it wants to read.
    • Bit 5 = 0: Select the Action buttons (A, B, Select, Start).
    • Bit 4 = 0: Select the Direction buttons (Right, Left, Up, Down).
  • Bits 3-0 (Read):The CPU reads these bits to see the status of the buttons in the selected row.Important:one bit to0means the button ispressed. one bit to1it means that it isloose.

The Entropy Loop:Many BIOSes and games, to initialize their random number generator (RNG), do not only use the Timer. They enter a loop that repeatedly reads the state of theJoypad (P1 register,0xFF00). They wait for the value to change, which happens unpredictably if the player is tapping the buttons during startup. This "noisy" reading provides an excellent entropy seed for the RNG.

Fountain:Pan Docs - Joypad Input, P1 Register

Implementation

The Joypad subsystem was implemented in C++ following the same architectural pattern as Timer and PPU: pure C++ class with Cython wrapper for exposure to Python.

Components created/modified

  • src/core/cpp/Joypad.hppandJoypad.cpp:C++ class that maintains the state of the 8 buttons (4 addresses + 4 actions) and handles the reading/writing of register P1.
  • src/core/cython/joypad.pxdandjoypad.pyx:Wrapper Cython that exposesPyJoypadto Python with methodspress_button(), release_button(), read_p1()andwrite_p1().
  • src/core/cpp/MMU.hppandMMU.cpp:Integration of the Joypad in the MMU to handle reads/writes in0xFF00.
  • src/viboy.py:Creation of instance ofPyJoypadand connection to the MMU in the main system.
  • src/gpu/renderer.py:Mapping Pygame keys to Joypad buttons in methodhandle_events().
  • setup.py:Inclusion ofJoypad.cppin the compilation of the C++ module.

Design decisions

Key Mapping:A standard Game Boy mapping was implemented:

  • Directions:Arrows (UP, DOWN, LEFT, RIGHT) → indices 0-3
  • Actions:Z/A (A button), X/S (B button), RETURN (Start), RSHIFT (Select) → indices 4-7

P1 reading logic:Register P1 has special behavior where bits 4-5 reflect the row selection, but when read, these bits may appear inverted depending on which row is selected. The correct logic was implemented based on unit tests.

Affected Files

  • src/core/cpp/Joypad.hpp- New C++ class for the Joypad
  • src/core/cpp/Joypad.cpp- Joypad implementation
  • src/core/cython/joypad.pxd- Cython definition of the Joypad
  • src/core/cython/joypad.pyx- Python Joypad Wrapper
  • src/core/cpp/MMU.hpp- Added pointer to Joypad and setJoypad() method
  • src/core/cpp/MMU.cpp- 0xFF00 read/write integration with Joypad
  • src/core/cython/mmu.pxd- Added Joypad forward declaration
  • src/core/cython/mmu.pyx- Added set_joypad() and import joypad method
  • src/core/cython/native_core.pyx- Included joypad.pyx
  • src/viboy.py- Creation of PyJoypad and connection to MMU
  • src/gpu/renderer.py- Pygame key mapping to Joypad
  • setup.py- Added Joypad.cpp to the build
  • tests/test_core_joypad.py- Complete suite of unit tests (8 tests)

Tests and Verification

Created a complete suite of unit tests intests/test_core_joypad.pywhich validates:

  • Initial state:Verify that the Joypad starts with all buttons released (P1 = 0xCF)
  • Address row selection:Verify that writing to P1 correctly selects the address row
  • Action row selection:Verify that writing to P1 correctly selects the action row
  • Multiple buttons:Verify that multiple buttons can be pressed simultaneously
  • Button release:Verify that the buttons can be released correctly
  • Integration with MMU:Verify that the MMU correctly reads/writes register P1 through the Joypad
  • All direction buttons:Validates each of the 4 direction buttons (Right, Left, Up, Down)
  • All action buttons:Validates each of the 4 action buttons (A, B, Select, Start)

Test results: 8 passed in 0.05s

Compiled C++ module validation:All tests are run against the compiled C++ module (viboy_core), confirming that the native implementation works correctly.

# Example of test executed:
def test_joypad_selection_direction():
    """Verify that writing to P1 selects the address row correctly."""
    joypad = PyJoypad()
    
    # Press Right (address, index 0)
    joypad.press_button(0)
    
    # Select address row (bit 4 = 0)
    joypad.write_p1(0x20)
    
    # Read Q1. It should show Right pressed (bit 0 = 0)
    result = joypad.read_p1()
    assert result == 0xDE #1101 1110

Sources consulted

  • Bread Docs:Joypad Input, P1 Register - P1 Register Specification and Button Array
  • Technical documentation:Behavior of register P1 in reading/writing

Educational Integrity

What I Understand Now

  • Button matrix:The Game Boy Joypad uses a 2x4 matrix where the CPU must scan rows to read the state of the buttons. This is more hardware efficient than having a dedicated pin for each button.
  • Entropy loop:Many games use the Joypad as an entropy source to initialize the RNG, repeatedly reading register P1 until they detect a change (user press).
  • Inverted logic:In register P1, a bit at 0 means button pressed, and a bit at 1 means button released. This is counterintuitive but is actual hardware behavior.

What remains to be confirmed

  • Joypad Interruptions:Register P1 can generate interrupts when a button is pressed. This will be implemented in a future Step.
  • Behavior on real hardware:Check if there is any special timing or bounce effect that should be considered.

Hypotheses and Assumptions

It is assumed that the reading behavior of register P1 is correct according to the unit tests. The 4-5 bit inversion logic when reading is based on the expected values ​​of the tests, which were designed according to the Pan Docs documentation.

Next Steps

  • [ ] Run the emulator and verify that the CPU exits the entropy loop when pressing a key
  • [ ] Verify that the Nintendo logo graphics appear on the screen after pressing a key
  • [ ] Implement Joypad interrupts (IF register bit 4)
  • [ ] Optimize key mapping to support multiple keyboard layouts