292. Cpubus controller 4016

Route CpuBus $4016 reads/writes to controller port 1.

Lesson 292 of 356 · tests/chapter_07_controller_input/test_292_cpubus_controller_4016.py

File to update

emulator/bus/cpu_bus.py

Why this step exists

The previous controller steps created a pure Controller object. Now the CPU bus must expose that controller through the NES memory-mapped controller port:

$4016 = controller port 1

Normal NES polling sequence

write 1 to $4016
write 0 to $4016
read $4016 eight times

Those eight reads return

A, B, Select, Start, Up, Down, Left, Right

References

https://www.nesdev.org/wiki/Standard_controller
https://www.nesdev.org/wiki/Controller_reading_code

Suggested implementation example

from emulator.input.controller import Controller


@dataclass
class CpuBus:
    ...
    controller_1: Controller = field(default_factory=Controller)

    def read(self, addr: int) -> int:
        ...

        # Controller port 1
        if addr == 0x4016:
            return self.controller_1.read_bit()

        # Controller port 2 / expansion input is out of scope for now.
        if addr == 0x4017:
            return 0

        ...

    def write(self, addr: int, value: int) -> None:
        ...

        # Controller port 1 strobe
        if addr == 0x4016:
            self.controller_1.write_strobe(value)
            return

        # $4017 writes are APU frame-counter writes, no-op for now.
        if addr == 0x4017:
            return

        ...

Important distinction

$4016 read/write belongs to controller port 1.
$4017 read is controller port 2 / expansion input, out of scope for now.
$4017 write is APU frame counter, out of scope for now.

Common misconception

"Controller input should be handled by pygame directly in CpuBus."

No. CpuBus should only talk to the pure Controller object. Pygame keyboard mapping will later update Controller button booleans from a manual/frontend entry point.

Out of scope

  • pygame keyboard mapping
  • controller port 2 implementation
  • Famicom expansion controllers
  • DMC/controller read glitch behavior
  • open bus upper-bit behavior

Run this lesson

uv run pytest tests/chapter_07_controller_input/test_292_cpubus_controller_4016.py -v