Skip to content

Experiments about GameBoy Advance and how cartridges work

License

Notifications You must be signed in to change notification settings

jojolebarjos/gba-cartridge

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GameBoy Advance cartridge

This project is about making homebrew cartridges for the GameBoy Advance, using an FPGA (here a TinyFPGA BX). Given the update frequency (and the fact that both falling-edges and rising-edges are meaningful), this is more convenient than bit-banging using some micro-controller. A few experiments are available, some making use of an additional micro-controller (here an Arduino Nano 33 BLE) for communication.

Cartridge protocol

The cartridge has the following pins:

  • 1: VCC, at 3.3V.
  • 2: PHI, the physical clock, which can be configured (disabled, 4.19MHz, 8.38MHz, 16.78MHz). Disabled by default. See waitstate control.
  • 3: ~WR, write control.
  • 4: ~RD, read control.
  • 5: ~CS, select ROM.
  • 6-21: AD, address and data.
  • 22-29: A, address or data.
  • 30: CS2, select SRAM.
  • 31: IRQ, interrupt request. Can be left unconnected or grounded.
  • 32: GND, ground.

General comments about memory accesses:

  • Data format is always little endian, i.e. least significant byte is first.
  • Depending on the requested (virtual) address and operation, the GameBoy will either assume ROM or RAM when accessing the cartridge.
  • ROM data access is 16-bits, hence data is aligned on 2-bytes steps. This means the implicit least-significant bit of the address is always 0 (i.e. A0..23 is the address divided by 2).
  • ARM instructions (32-bits) are slow to fetch. It is better to use THUMB instructions, or copy the code in WRAM.
  • GamePak accesses can be either sequential or non-sequential. The first access to a random location in ROM must be non-sequential.
  • The GameBoy will wait a few cycles when accessing memory. Up to 3 configurations can be used (useful if the cartridge has multiple ROM units with different physical properties). By default, it will assume 4 cycles for random access, and then 2 cycles for sequential access. See waitstate control.
  • When doing ROM access, the AD bus is used in both direction. The cartridge should actively write only when both ~CS and ~RD are low.
  • When doing RAM access, the AD bus is used for the 16-bits address, and the 8-bits data is returned through A16..23. ~CS2 is used instead of ~CS.

A read access from the ROM is done as follows:

  • On ~CS falling-edge, the cartridge latches A0-A23 as address.
  • On ~RD falling-edge, the cartridge fetches the data at latched address and outputs D0-D15. The output must be ready by next rising-edge of ~RD (depends on waitstates).
  • On ~RD rising-edge, the cartridge increments latched address by one (i.e. allow sequential read by just strobing ~RD).
  • On ~CS rising-edge, the transaction is done.

While it usually makes no sense to write to a ROM, the GameBoy will submit write commands in a similar fashion, toggling ~WR instead. In this project, we use that to let the software write back to the cartridge, for instance to report key input.

Therefore, a simple read-only GamePak ROM does not use PHI, ~WR, ~CS2 and IRQ.

Cartridges that require more than 64K of storage will need to use bank switching to map more memory. This topic has not been investigated in this project.

Relevant links:

Compiling a GameBoy Advance game

The GameBoy Advance features an ARM-based processor. We will use devkitPro to compile binaries.

The most straightforward approach is to use the Docker image. We share the local folder with the container, to avoid unnecessary copies.

# Run in interactive mode
docker run -ti -v ${PWD}:/rom devkitpro/devkitarm
# On Windows: docker run -ti -v %cd%:/rom devkitpro/devkitarm

# Add compiler binaries to path
export PATH="/opt/devkitpro/devkitARM/bin/:$PATH"

# Go to shared folder and build files
cd /rom/
make

Links related to GBA software development:

Emulators and GameBoy implementations:

Development PCB

In order to have a flexible playground, a custom-made PCB has been designed using KiCad and ordered on JLCPCB. This cheap cartridge header can be soldered to connect an actual cartridge, if needed. A simple casing has been designed using FreeCAD and 3D-printed (resin) to properly fit the GameBoy Advance. Depending on whether a GameBoy Advance and/or a cartridge is used, the FPGA can have various roles.

  • The 32 pins of the cartridge are forwarded from the GameBoy to the cartridge.
  • Two sets of connectors are available to intercept (or drive) the pins: a 2x16 header for generic usage, and a 2x14 header designed to hold the TinyFPGA BX.
  • A dedicated header is available to configure power supply.
  • A is not exposed to the TinyFPGA BX, as there are not enough pins. Note that some GPIO pins are exposed as pads by the FPGA, but this is not convenient for this project.
  • Instead, some pins are exposed as "extra", and can be used either for external communication (e.g. SPI) or connected to A to extend the address space.
  • Use ENIG finish (i.e. not HASL), for more strength.
  • In order to fit in the opening, a thickness of 0.8mm should be used (as the casing already takes roughly 1mm).

See the schema for more details.

PCB (front)

PCB (back)

Getting started with TinyFPGA BX

As described in the User Guide, install APIO:

conda create -n fpga python=3.7
activate fpga
pip install apio==0.4.0b5 tinyprog
apio install system scons icestorm iverilog

Connect the board and run:

apio install drivers
apio drivers --serial-enable

Reconnect the board, and check that the device is recognized. Windows might take a few seconds to actually detect and properly setup the serial device.

apio system --lsserial

Make sure the board is up-to-date:

tinyprog --update-bootloader

For better integration, use Atom and install the apio-ide package. Note that you should run Atom inside the Conda environment, so that paths are properly set up:

atom --new-instance

Using the default template, you can quickly start developing the TinyFPGA.

Note that you can upload the code by calling tinyprog directly:

tinyprog --program hardware.bin

The apio-ide package provide shortcuts for the most common operations, i.e. build and program. Use F8 to show the compilation log after a successful build.

Links:

Experiments

A few simple experiments have been made until now.

Cartridge dump

An Arduino Nano 33 BLE is connected to a TinyFPGA BX over SPI.

Hardware setup

Name Arduino Nano 33 BLE Wire Board TinyFPGA BX
GND GND black GND GND
MISO D2 (2) green EXT0 1 (A2)
MOSI D3 (3) blue EXT1 2 (A1)
SCK D4 (4) white EXT2 3 (B1)
SS D5 (5) yellow EXT3 4 (C2)

The cartridge is powered by the TinyFPGA BX, i.e. VCC_CART is connected to VOUT.

This is a naive implementation of the above-mentioned protocol:

  • The FPGA toggles ~CS and ~RD, writes the address and reads the output through AD. Note that, as A is ignored, only the first 131072 can be read.
  • The Arduino acts as a bridge, communicating with the FPGA using SPI and with the computer using USB.
  • A Python script sends the commands and dumps the content to a binary file.

Note that this approach does not handle any memory bank. Therefore, it cannot be used as-is to dump a whole cartridge.

See ./experiments/dump/ for more details.

Read-only cartridge

The TinyFPGA BX acts as a read-only ROM cartridge. A simple "game" is provided, where a white dot is moved using the arrows.

Hardware setup

See ./experiments/read/ for more details.

Read-write

The TinyFPGA BX acts as a writable ROM cartridge. A simple "game" is provided, where buttons are written at each frame to the cartridge memory.

See ./experiments/write/ for more details.

USB gamepad

The TinyFPGA BX acts as a writable ROM cartridge. A simple "game" is provided, where buttons are written at each frame to the cartridge memory. This information is then forwarded to an Arduino Nano 33 BLE, which is recognized as a HID gamepad.

Hardware setup

TODO: use HID over USB/BLE

TODO: use interrupts on GBA (and sleep when no button is pressed)

TODO: make FPGA the SPI master, or use interrupts to let the Arduino communicate only when needed

See ./experiments/gamepad/ for more details.

Direct Memory Access

...

http://problemkaputt.de/gbatek-gba-dma-transfers.htm https://gbdev.gg8.se/wiki/articles/Video_Display https://austinjadams.com/blog/autograding-gba-dma/ https://badd10de.dev/notes/gba-programming.html

See ./experiments/dma/ for more details.

VGA

The GBA has a TFT color LCD that is 240 x 160 pixels in size and has a refresh rate of exactly 280896 cpu cycles per frame, or around 59.73 hz. Most GBA programs will need to structure themselves around this refresh rate. The LCD can display 15-bit RGB colors (0bxbbbbbgggggrrrrr).

TODO:

The closest standard mode is 720x480 @60Hz RGB 4:4:4