Build Guides Programming Manual Browse games Wiki Blog
Hardware Repo Emulator Repo

Programmer's Manual

Table of contents:

System Overview

The GameTank is based on the W65C02S from WDC. It features two of these processors as well as the W65C22 Versatile Interface Adapter ("VIA"). The system also has composite video output, mono audio output, two front-facing controller ports, a 14-pin rear-facing accessory header, and a top-loading 36-pin cartridge port. As a 6502-based system the boot behavior is for the CPU to read a memory address from 0xFFFC and 0xFFFD and then begin executing code from that address. The VIA controls twelve general-purpose IO pins on the rear accessory port, and four pins on the cartridge port.

The GameTank has dual 128x128 framebuffers which are transmitted as color composite video by a dedicated circuit running in parallel to the CPU. The framebuffers store each pixel as a single byte that encodes roughly the hue, saturation, and luminosity components of a color. The system also has dedicated hardware, referred to as the "Blitter", for copying graphic assets into the framebuffer. Separate from both the framebuffer RAM and general-purpose RAM is Sprite RAM. The Blitter strictly copies from Sprite RAM to the framebuffer.

The second 6502 CPU is dedicated to the task of generating audio output and operates using RAM shared by the main CPU. It has no permanent storage, and must be initialized with its own program by the main program on the cartridge. This program will provide an interrupt service routine that computes the next audio sample and sends its value to the Digital-to-Analog Converter.

The cartridge port is given control over the entire upper half of the 6502's 64K address range. At time of writing, the typical cartridge hardware carries 2 megabytes of flash memory and a shift register used for selecting a portion of those 2 megabytes to be exposed to the system bus.

Memory Map

Addr Use
$0000 - $1FFF General purpose RAM
$2000-$2007 System control registers
$2008 - $2009 Gamepads
$2800 - $280F Versatile Interface Adapter (GPIOs, Timers)
$3000 - $3FFF Audio RAM
$4000 - $7FFF Framebuffer, Sprite RAM, Blitter registers
$8000 - $FFFF Cartridge slot

System Registers

Addr Use
$2000 Write 1 to reset audio coprocessor
$2001 Write 1 to send NMI to audio coprocessor
$2005 Banking Register
$2006 Audio enable and sample rate
$2007 Video/Blitter Flags
$2008 Gamepad 1 (Left port)
$2009 Gamepad 2 (Right port)

The registers from $2000 through $2007 are write-only. The two gamepad registers are read-only.

Banking Register

The Banking Register controls certain memory mapping behaviors within the console hardware.

Bitmask Use
00000111 Select the active Sprite RAM page
00001000 Select which framebuffer to read/write/blit
00010000 Clip blits on the left/right screen edges
00100000 Clip blits on the top/bottom screen edges
11000000 Select general purpose RAM page

The active Sprite RAM page applies to both CPU access of Sprite RAM and blit operations.

Clipping blits refers to DMA draw operations that would cross the boundary of the screen. By default the drawing will wrap around to the other side of the screen, enabling the respective clip bit will prevent this behavior.

The banking for general purpose RAM includes the zero page and 6502 stack page, so you'll need to restore the RAM bank before returning from a subroutine that changes the RAM page.

Video and Blitter Flags

Bitmask Name Use
00000001 DMA_ENABLE Enable/disable the Blitter
00000010 DMA_PAGE_OUT Select framebuffer page sent to TV
00000100 DMA_NMI Enable NMI signal generated by VBlank
00001000 DMA_COLORFILL_ENABLE Use solid colors for blits instead of sprites
00010000 DMA_GCARRY Set 0 to repeat 16x16 tiles on blit draws
00100000 DMA_CPU_TO_VRAM 0 means CPU accesses Sprite RAM
1 means the CPU access the framebuffer
01000000 DMA_IRQ Enable IRQ signal when blits finish
10000000 DMA_OPAQUE Set 1 to disable transparency

DMA_ENABLE must be set to access blitter registers and perform blitter operations. DMA_ENABLE must be clear for the CPU to access Sprite RAM or the framebuffer directly.
NOTE: Modifying DMA_ENABLE or any blitter register is not currently modeled by the GameTank Emulator.

DMA_PAGE_OUT can cause screen tearing if you use it outside of the TV's blanking period. Screen tearing is not currently modeled by the GameTank Emulator.

DMA_NMI controls whether the NMI interrupt will be called when the video hardware sends a sync pulse to the TV. This signal happens approximately 60 times per second.

DMA_COLORFILL_ENABLE when set will draw rectangles of a solid color.

DMA_GCARRY controls whether the sprite coordinate counters in the blitter will increment the upper four bits when the lower four bits roll over. Carrying is required to draw an image larger than 16x16, but turning it off can be used to repeat a 16x16 tile over a larger area.

DMA_CPU_TO_VRAM only applies when DMA_ENABLE is clear. This flag determines whether the CPU accesses the Sprite RAM or the Framebuffer RAM. These accesses are further controlled by the Sprite Page select and Framebuffer select bits in the Banking Register.

DMA_IRQ controls whether an IRQ interrupt will be called when a blit operation completes. Unlike NMI which is edge-sensitive, IRQ is level-sensitive. Before an IRQ service routine returns, the IRQ condition should be cleared or else the routine will immediately be called again. The IRQ condition will be cleared if 0 or 1 is written to the blit start register.

DMA_OPAQUE controls whether the blitter draws with transparency. When DMA_OPAQUE is zero the blitter will not write any zero-valued bytes to the framebuffer.

Blitter Operation

The Blitter is an arrangment of logic gates, registers, and counters that may be used to copy rectangular areas up to 127x127 pixels from Sprite RAM to the framebuffer. This copy runs in parallel to CPU execution and copies at a rate of 1 pixel per CPU clock cycle. There are seven registers used for preparing a copy operation and an additional address used for initiating the copy operation. If the DMA_IRQ flag is set, the Blitter will assert an IRQ signal when a copy operation completes.
In order to access these registers, DMA_ENABLE must be set. Modifying these registers during a copy operation is possible, but not presently modeled in the GameTank Emulator.

When the system is powered on the contents of Sprite RAM are, for all intents and purposes, random. Before drawing anything useful the CPU will need to copy graphics into this memory while the Blitter is disabled.

Addr Name Use
$4000 VX X coordinate in framebuffer of left column of drawing
$4001 VY Y coordinate in framebuffer of top row of drawing
$4002 GX X coordinate in Sprite RAM for first column of source data
$4003 GY Y coordinate in Sprite RAM for first row of source data
$4004 WIDTH Width of drawing operation. Bit 7 controls horizontal flipping.
$4005 HEIGHT Height of drawing operation. Bit 7 controls vertical flipping.
$4006 START Write 1 to clear IRQ and begin a blit operation. Write 0 to clear IRQ without starting a blit.
$4007 COLOR Value to use for Color Fill Mode. Only used when DMA_COLORFILL_ENABLE is set.

A typical blitting operation consists of setting each of the paramter registers, and then writing 1 to the START register. However, these parameters are not modified by the Blitter so if you are performing repetitive drawings that have common parameters you can skip setting the reused values for increased performance.

During a blit the parameters should generally be left alone. If you know the drawing is small you can simply wait by performing some other work on the CPU. For larger or variable sized drawings you can use an IRQ handler to set up blits. You can also execute a WAI ("wait") opcode that will pause the CPU until the next interrupt of any kind occurs. You lose out on parallelism this way, but it makes things very simple. For consecutive small drawings the overhead of managing a drawing queue can waste more time than simply waiting for the blitter to finish.

Setting the higest bit of WIDTH or HEIGHT will invert the output of the GX or GY counters. This is used for drawing a horizontally and/or vertically flipped version of a sprite, though it requires some modification of the GX and GY register values to use for this purpose.

Values of VX and VY outside of the 128x128 resolution are valid. If CLIP_X or CLIP_Y are set in the banking buffer then blit writes that fall outside the 0-127 range will simply not be drawn.

Sprite RAM can be thought of as a set of eight 256x256 sheets, selected using the Banking Register. Therefore any value for GX and GY in the full 0-255 range is valid and practical.

The values of GX and GY last used by the Blitter will also influence which part of Sprite RAM the CPU can access in addition to the Banking Register. The video section of the memory map is only big enough for a 128x128 region. So the quadrant of Sprite RAM available to the CPU is determined by the most significant bit in the Sprite RAM coordinate counters. Typically these would be set before loading sprites by running a single-pixel blit operation copying from the target quadrant to an off-screen portion of the framebuffer.

Audio Coprocessor

All audio on the GameTank is produced by a subsystem called the Audio Coprocessor. It is essentially a minimal 6502-based computer enclosed within the larger system. This subsystem has 4 kilobytes of RAM, all of which is accessible to the main system at any time. Due to its simplicity, the Audio Coprocessor runs at four times the clock speed of the main system. While the Audio Coprocessor is enabled, IRQ signals will be asserted by a configurable counter. The audio sample rate register at $2006 will set the rate of these IRQ signals with its lower seven bits. The highest bit determines whether the Audio Coprocessor is enabled or suspended. It is a good idea to suspend the Audio Coprocessor while initializing Audio RAM.

From the perspective of the Audio Coprocessor, a program should only treat the memory range from $0000 through $0 through $0FFF as available. Any reads or writes will be wrapped around this range.
However, any writes to $8000 will additionally be mirrored to the DAC buffer. The value stored in the DAC buffer will be copied to the DAC output simultaneously with the next IRQ event.

The typical Audio Coprocessor program consists of a main loop that does very little, and a long IRQ handler that computes the next audio sample before writing it to the DAC buffer.


The GameTank uses two Genesis-style controller ports on the front of the console. The 9-pin controller ports include a "select" line that tells the controller which button states should be connected to the output. Each controller port will alternate this "select" value on consecutive reads. Additionally reading one controller port will reset the "select" value on the other controller port, to allow placing it into a known state before reading.

In other words: Before reading controller port 1, read controller port 2 and discard the result. Then the next two reads to controller port 1 will contain the full state of a standard controller.

This does mean that 6-button Genesis controllers may also be readable but it hasn't been tested yet.

Versatile Interface Adapter

The GameTank includes a 6522 Versatile Interface Adapter, or "VIA" for short. This chip is primarily used for controlling the GPIO pins on the cartridge port and on the accessory header.

Addr Name Use
$2800 ORB/IRB Output/Input Register B
$2801 ORA/IRA Output/Input Register A. Bits 0, 1, 2, and 7 are exposed to the cartridge port.
$2802 DDRB Data Direction Register B
$2803 DDRA Data Direction Register A
$2804 T1C-L T1 Low-Order Latches (Write) or Counter (Read)
$2805 T1C-H T1 High-Order Counter
$2806 T1L-L T1 Low-Order Latches
$2807 T1L-H T1 High-Order Latches
$2808 T2C-L T2 Low-Order Latches (Write) or Counter (Read)
$2809 T2C-H T2 High Order Counter
$280A SR Shift Register
$280B ACR Auxiliary Control Register
$280C PCR Peripheral Control Register
$280D IFR Interrupt Flag Register
$280E IER Interrupt Enable Register
$280F ORA/IRA Same as ORA/IRA but without "handshake"

NOTE: Not all of these are currently modeled in the GameTank Emulator as I've primarily been using ORA for cartridge banking. If you have ideas to try with other VIA registers feel free to bring them up on the Discord and motivate adding them to the emulator!
See the WDC datasheet for more specifics on this chip's behavior.

See the section on 2MB Flash Cartridges for information on how this device is used to access cartridge ROM banks.

The GameTank Emulator uses the rear accessory port to activate profiling timers for debugging program performance.

2MB Flash Cartridges

The currently available cartridge designs are the 8KB EEPROM cartridges and the 2MB NOR Flash cartridges. The 8KB EEPROM cartridges fit neatly into the 32K available through the cartridge port, and thus don't leave much to talk about.
The 2MB Flash cartridges present the issue of how to access more than 32 kilobytes through a 32 kilobyte window. A banking system is thus used to address this large memory span. On the cartridge board is a shift register that is accessed through the lowest three bits of VIA Output Register A.

Bit Use

The shift register is written with repeated writes to ORA. Each time CLOCK goes from 0 to 1, the value of DATA is pushed into the shift register.
When LATCH goes from 0 to 1, the value of the shift register will take effect.

On the 2MB cartridge only the lowest seven bits in the shift register are considered. For memory reads in the range $8000 through $BFFF, the bits from the shift register will be used as the highest address bits on the NOR Flash. This provides a moveable window into the Flash memory with 128 possible positions.

To allow for deterministic boot behavior, memory reads in the range $C000 through $FFFF will always map to the same memory as Bank 127. This ensures that the 6502 CPU will be able to retrieve the intended RESET, IRQ, and NMI pointers no matter the state of the cartridge banking register.

NOTE for compatibility with future cartridges:

Another cartridge under development adds battery-backed RAM, and makes use of the 8th shift register bit. When the 8th bit is zero, reads (and writes) will be mapped to the cartridge RAM. Therefore it is recommended to use bank numbers 128-255 in your code, instead of 0-127. On the RAM-less design these ranges are equivalent, so the high bit being set will not cause an error.