Video summary

Gameboy Emulator Development - Part 12

Main summary

Key takeaways

Technology

Summary (Technological concepts & implementation details)

This video is part of a Game Boy emulator development series, focusing on wiring up the PPU LCD registers and creating a PPU LCD mode state machine. It builds infrastructure needed before full pixel rendering.


1) LCD register wiring (PPU ↔ bus interface)

LCD register set added via lcd.h

A new header (lcd.h) is created to define the LCD-related memory-mapped registers in bus order (so read/write can be done by computing an offset rather than many if/else blocks).

Key registers and purposes:

  • LCDC / LCD control at 0xFF40 Bits control enabling background/window/sprites, sprite height (8 vs 16), and overall LCD enable (bit masks via macros).

  • STAT / LCD status at `0xFF41**

    • Low bits indicate the current LCD mode.
    • LYC interrupt flag logic: sets/clears when LY == LYC.
    • Interrupt enables for specific STAT events (multiple STAT interrupt sources).
  • Scroll registers
    • SCY at 0xFF42 (scroll Y)
    • SCX at 0xFF43 (scroll X)
  • Line counters
    • LY at 0xFF44 = current scanline
    • LYC at 0xFF45 = compare register; if LY == LYC, set STAT flag and potentially raise interrupt.
  • DMA already known at 0xFF46
  • Palettes
    • Background palette at 0xFF47
    • Sprite palettes at 0xFF48 and 0xFF49 (OBJ palettes)
  • Window position registers
    • WX/WY at 0xFF4A (window Y/X; described as window position—video notes it will be discussed later)

Palette storage & initialization

The LCD context includes:

  • Palette color entries (default colors like white/light gray/dark gray/black)
  • Palette update logic based on the hardware rule: each palette byte encodes 4 colors using 2 bits per color index.
  • For sprite palettes, the rule differs: color index 0 is transparent (ignored), implemented by not mapping it to a real color like the background does.

Macros for LCDC and STAT bit extraction

Macros are created to extract/identify flags such as:

  • Background/window enable (LCDC bit 0)
  • Object/sprite enable (LCDC bit 1)
  • Object height (LCDC bit 2; selects 8 vs 16)
  • LCD enable (LCDC bit 7, referenced via bit macros in comments)
  • STAT mode extraction from low bits
  • STAT interrupt enable bits (shifted so checking them is straightforward)

2) lcd.c implementation: context, read/write, special handling

LCD context initialization defaults

On lcd_init():

  • LCDC set to 0x91
  • Scroll X/Y set to 0
  • LY and LYC set to 0
  • Default palettes:
    • Background palette default 0xFC
    • Sprite palettes default 0xFF each
  • Video/debug color initialization sets default palette entries (white → black).

Bus reads/writes without branching

Because registers are stored in bus order, the code:

  • For reads: computes offset = address - 0xFF40 and returns a byte from the LCD context array.
  • Avoids many conditional blocks.

Bus write special cases

In lcd_write():

  • If offset corresponds to 0xFF46, trigger DMA start.
  • If offset is within palette register range (0xFF470xFF49), update palette entries by decoding the 2-bit color fields.
  • Palette update logic assigns palette indices 0–3 using shifts and masks.

3) Bus wiring (io.c): route LCD register accesses

In the IO bus layer:

  • Reads: if address is between 0xFF40 and 0xFF4B, route to lcd_read().
  • Writes: same range routes to lcd_write() (replacing a prior hack with proper routing).

This is described as providing a “good start” to test basic emulator behavior.


4) Create a PPU LCD mode state machine

LCD modes enumeration (implemented via state machine header)

A separate ppu_sm.h (planned/created) defines modes:

  • Mode OAM (object attribute scan)
  • Mode X for pixel transfer (video uses a nonstandard name like “extra/trans”; conceptually corresponds to transfer)
  • Mode HBlank
  • Mode VBlank

These correspond to the Game Boy PPU LCD state progression.

PPU context additions

Inside the ppu context initialization:

  • Game Boy timing constants:
    • Lines per frame: 154
    • Ticks per line: 456
  • New PPU fields:
    • current_frame
    • line_ticks (ticks elapsed within the current line)
    • video_buffer allocation (resolution × bits-per-pixel; pixel rendering not implemented yet in this part)

PPU tick loop

On each emulator tick:

  • line_ticks++
  • Mode transitions call the relevant state handler:
    • ppu_mode_oam
    • ppu_mode_x (transfer)
    • ppu_mode_hblank
    • ppu_mode_vblank

Mode transition rules (simplified for now)

Implemented logic is intentionally partial—enough to switch between states correctly before pixel pipeline work.

Key behaviors:

  • OAM → Transfer after ~80 ticks (matching OAM scan duration from the pixel FIFO/mode description).
  • Transfer → HBlank after additional ticks (uses 80 + 172 as a threshold; described as covering minimal drawing/transfer time).
  • VBlank / LY updates
    • When enough ticks/lines have elapsed, increment LY.
    • If LY == LYC, set STAT flag.
    • If corresponding STAT interrupt enable bit is set, request a STAT interrupt.
    • Transition to new frame once LY exceeds the frame’s last visible line count (144 is referenced for visible region; lines per frame is 154).
    • Request VBlank interrupt when entering VBlank at the proper LY boundary.
  • After frame completion:
    • Reset LY to 0
    • Reset line_ticks
    • Return mode to OAM

5) Frame timing and UI update optimization

FPS limiting / frame pacing (~60 FPS)

The PPU logic tracks time using a planned get_ticks() helper (SDL-based), implementing:

  • Target frame time = 1000/60 milliseconds
  • Delay if the frame finished early
  • Every second, compute and print FPS using frame counters

Update UI only once per frame

In emu.c:

  • Instead of updating the UI very frequently, it calls ui_update only when ppu.current_frame changes.
  • ppu_tick() is hooked into the emulator’s timer tick path.

Outcomes / testing notes

After wiring LCD registers and PPU ticks:

  • Games/apps like Dr. Mario start running enough to show some activity.
  • There are “unsupported bus rides/writes” because not all IO/register behavior is implemented yet.
  • Some animations/debug output appear, indicating the timing and CPU/PPU integration are functioning at least partially.

The video ends by stating that the next step is the pixel processing pipeline, beginning with background rendering, then sprites/windows later.


Main speakers / sources

  • Low Level Devil (channel/series narrator: “low level devil channels game boy emulator development series”)
  • Official Game Boy documentation / Pan Docs (referenced throughout for timing and register bit meanings)
  • SDL (SDL get ticks) for timing/fps pacing
  • Code modules referenced: CPU, IO, DMA, PPU (as implementation sources within the project)

Original video