Capturing the Gameboy LCD with an FPGA

Super Mario Land 2: 6 Golden Coins title screen Mario jumping Mario about to dodge a rock Mario underwater

Lemmings title screen Mario and Yoshi title screen Xenon II Mario flying

There are 6 important outputs from the Gameboy main processor to the LCD for capturing the pixels:

In brackets I've put the names and pin numbers for these signals from the Gameboy schematic. You can tell these are the ones required as they are the ones connected to the ICD2-R chip on the SGB (SGB schematic). Although only D0, D1, CLK and CPL are strictly required.

Don't forget that the Gameboy is 5 volt TTL: do NOT connect these directly to a non-5v tolerant input! I am using a Papilio Pro FPGA development board with an IO Buffer wing to handle the logic level conversion. I have soldered an old IDE ribbon cable to the pins of the ribbon cable which connects the main board to the LCD board of the Gameboy.

Gameboy connected to FPGA
Gameboy connected to FPGA

The pins to connect to are shown quite well in this image from the Nintendoscope project:

LCD ribbon cable pins
Original source Nintendoscope

For testing I captured the signals using my Papilio Pro as a logic analyser with the Open Bench Logic Sniffer bitfile and sump-dump:

./sump-dump /dev/ttyUSB1 groups 1 trigger 0x20=0x20 divisor 10 vcd data=0x3 vcd clk=0x4 vcd hsync=0x10 vcd vsync=0x20 vcd cpl=0x40 vcd c=0x80

The signal buffer isn't large enough to capture a whole frame, it can only store about a third of a frame.

Logic analyser capture of two lines of pixels
First two lines of a frame (vcd)
Logic analyser capture of the end of a frame
Last few lines of a frame (vcd)

The behaviour of these signals is:

The pixel data changes very soon after the rising edge of CLK for the next pixel - so soon that to the logic analyser it appears to happen at the same time as the rising edge. So I actually capture the D0 & D1 values from the cycle before the rising edge is detected.

Capturing D0 & D1 on the falling edge of CLK seems to be widely reported as the correct thing to do, but this will capture the whole frame shifted left by one pixel. It is definitely easier though, so if you are happy with 159 pixels it is a decent approach.

Hardware

Used migen to build hardware, available here: gbcap. It's set up to build for the Papilio Pro, will require some changes to build with other FPGA boards.

This code sets up the pin assignment (from gbcap.py):

 plat.add_extension([
 ('gb_lcd', 0,
     Subsignal('vsync', Pins('B:5')),
     Subsignal('hsync', Pins('B:4')),
     Subsignal('cpl', Pins('B:6')),
     Subsignal('clk', Pins('B:2')),
     Subsignal('pixel', Pins('B:0', 'B:1')))])

i.e. D0 should be connected to pin 0 of the B wing header, and so on.

Once the bitfile is programmed onto your FPGA it will wait for a byte to be received over the UART and then start capturing frames. You can grab these using the recv.py script in the git repo, it will dump out pgm files.

The UART is only running at 2Mbaud in the current design, which is not fast enough to stream the Gameboy frame captures in real-time (that would require 60 * 160 * 144 * 2 = 2764800 bits per second), so you will only get a few frames before it stops due to FIFO overflow.

Previous Work

↩ Home