245. Ppuscroll second write toggle
Implement PPUSCROLL ($2005) two-write scroll behavior.
Lesson 245 of 356 · tests/chapter_03_ppu_memory_and_graphics_data/test_245_ppuscroll_second_write_toggle.py
Implement PPUSCROLL ($2005) two-write scroll behavior. !! Take your time to implement this
References
https://www.nesdev.org/wiki/PPU_registers#PPUSCROLL
https://www.nesdev.org/wiki/PPU_scrolling#$2005_(PPUSCROLL)_first_write_(w_is_0)
https://www.nesdev.org/wiki/PPU_scrolling#$2005_(PPUSCROLL)_second_write_(w_is_1)File to update
emulator/ppu/ppu.pyMethod to update
PPU.write_register(addr, value)Why this step exists
PPUSCROLL is the CPU-visible register at $2005. Like PPUADDR ($2006), it uses the shared first-write / second-write toggle:
second_write_toggle == False -> first write
second_write_toggle == True -> second writeThe first write configures horizontal scroll
value bits 7-3 -> coarse X, stored in temp_vram_addr bits 0-4
value bits 2-0 -> fine X, stored in fine_xThe second write configures vertical scroll
value bits 7-3 -> coarse Y, stored in temp_vram_addr bits 5-9
value bits 2-0 -> fine Y, stored in temp_vram_addr bits 12-14Important internal-register model
vram_addr -> v, current VRAM address
temp_vram_addr -> t, temporary VRAM address
fine_x -> x, fine horizontal scroll
second_write_toggle -> w, first/second write toggleSuggested implementation pseudocode, matching the explicit bit-moving style:
case 0x2005:
# Keep for old compatibility.
self.scroll = value
if not self.second_write_toggle:
# Reference:
# https://www.nesdev.org/wiki/PPU_scrolling#$2005_(PPUSCROLL)_first_write_(w_is_0)
#
# PPU Scroll first write.
# Incoming value bits are named ABCDEFGH.
#
# fine_x register:
# x: FGH <- value: .....FGH
#
# In other words, save the last 3 bits as fine horizontal scroll.
self.fine_x = value & 0b0000_0111
# temp_vram_addr register:
# t: ....... ...ABCDE <- value: ABCDE...
#
# In other words, save the upper 5 bits as coarse X in bits 0-4.
# The mask keeps every temp_vram_addr bit except coarse X.
self.temp_vram_addr = (
(self.temp_vram_addr & 0b1111_1111_1110_0000)
| (value >> 3)
)
# w <- 1
# Next $2005/$2006 write will be treated as the second write.
self.second_write_toggle = True
else:
# Reference:
# https://www.nesdev.org/wiki/PPU_scrolling#$2005_(PPUSCROLL)_second_write_(w_is_1)
#
# PPU Scroll second write.
# Incoming value bits are named ABCDEFGH.
#
# temp_vram_addr register:
# t: FGH..AB CDE..... <- value: ABCDEFGH
#
# Step 1: keep only untouched bits:
# - bit 15
# - nametable bits 10-11
# - coarse X bits 0-4
#
# This clears the vertical scroll destination bits:
# - coarse Y bits 5-9
# - fine Y bits 12-14
self.temp_vram_addr = (
self.temp_vram_addr & 0b1000_1100_0001_1111
)
# Step 2: put the last 3 bits into fine Y.
# value: .....FGH
# FGH << 12 -> bits 12-14
self.temp_vram_addr = (
self.temp_vram_addr | ((value & 0b0000_0111) << 12)
)
# Step 3: put the upper 5 bits into coarse Y.
# value: ABCDE...
# (value & 11111000) << 2 -> bits 5-9
self.temp_vram_addr = (
self.temp_vram_addr | ((value & 0b1111_1000) << 2)
)
# w <- 0
# The two-write PPUSCROLL sequence is complete.
self.second_write_toggle = FalseOut of scope
- copying scroll bits from temp_vram_addr to vram_addr during rendering
- scanline/cycle timing
- actual background rendering
Run this lesson
uv run pytest tests/chapter_03_ppu_memory_and_graphics_data/test_245_ppuscroll_second_write_toggle.py -v