Skip to content

PPU VBlank signal not reaching CPU - Clock domain crossing issue #7

Description

@itongxiaojun

Problem

Games cannot initialize because VBlank signal is not reaching the CPU correctly.

Symptoms

  • Games stuck in infinite loop waiting for VBlank
  • $2002 (PPUSTATUS) always reads VBlank=0
  • No VRAM/palette writes occur
  • Games never display graphics

Root Cause Analysis

✅ PPU Module Works Correctly

Isolated PPU test shows VBlank generates properly:

✅ Level 0.2: VBlank Signal Test - PASS
- VBlank SET at scanline 241
- VBlank CLEAR at scanline 261  
- Triggers every frame correctly

❌ NES System Integration Broken

When integrated in nes_system.sv, VBlank signal fails to reach CPU:

[PPU] $2002 read: VBlank=0 status=$00  ← Always 0!
[PPU] $2002 read: VBlank=0 status=$00
[PPU] $2002 read: VBlank=0 status=$00

Technical Details

Clock Domain Crossing Issue

// nes_system.sv
assign cpu_clk = clk_div[2];  // ÷8 = 2.68 MHz
assign ppu_clk = clk_div[1];  // ÷4 = 5.37 MHz

// VBlank synchronizer (ppu_clk -> cpu_clk)
logic vblank_sync1, vblank_sync2, vblank_sync3;

always_ff @(posedge cpu_clk) begin
    vblank_sync1 <= vblank;      // From PPU
    vblank_sync2 <= vblank_sync1;
    vblank_sync3 <= vblank_sync2;
end

// PPUSTATUS update
if (vblank_sync2 && !vblank_sync3) begin
    ppustatus[7] <= 1;  // Set VBlank flag
end

Problem: Synchronizer not working - ppustatus[7] never gets set.

Test Evidence

Layered Test Results

Level Test Status
0.1 PPU clock/counters ✅ PASS
0.2 VBlank signal (isolated) ✅ PASS
0.3 Video output signals ✅ PASS
1.x PPU registers ❌ BLOCKED
2.x VRAM access ❌ BLOCKED

Game Behavior

All games stuck at initialization:

WAIT_VBLANK:
    LDA $2002      ; Read PPUSTATUS
    AND #$80       ; Check VBlank bit
    BEQ WAIT_VBLANK ; Loop forever ← Stuck here!

Impact

  • Game compatibility: 0% (games cannot initialize)
  • PPU rendering: Broken (no VRAM data)
  • Previous 90% claim: Invalid (based on frame count, not actual functionality)

Proposed Fix

  1. Debug VBlank signal propagation from PPU to CPU
  2. Verify clock domain crossing logic
  3. Check if vblank signal is stable across clock domains
  4. Consider using proper CDC (Clock Domain Crossing) techniques
  5. Add debug signals to trace VBlank path

Files Involved

  • src/main/rtl/nes_ppu.sv - VBlank generation (✅ works)
  • src/main/rtl/nes_system.sv - VBlank synchronization (❌ broken)
  • Lines 402-433 in nes_system.sv

Test Case

# Run VBlank test
cd src/test/rtl
verilator --cc --exe --build ../../main/rtl/nes_ppu.sv test_ppu_vblank.cpp -o Vtest_vblank
timeout 5 ./obj_dir/Vtest_vblank

Expected: ✅ PASS (PPU works)

# Run game
timeout 3 src/test/rtl/obj_dir_screenshot/Vnes_system games/DonkeyKong_mapper0.nes /dev/null 2>&1 | grep "2002 read"

Expected: VBlank=1 at least once per frame
Actual: VBlank=0 always ❌

Priority

CRITICAL - Blocks all game functionality

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions