Table of contents:
- System Overview
- Memory Map
- System Registers
- Banking Register
- Video and Blitter Flags
- Blitter Operation
- Audio Coprocessor
- Versatile Interface Adapter
- 2MB Flash Cartridges
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.
|$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|
|$2000||Write 1 to reset audio coprocessor|
|$2001||Write 1 to send NMI to audio coprocessor|
|$2006||Audio enable and sample rate|
|$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.
The Banking Register controls certain memory mapping behaviors within the console hardware.
|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
|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.
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.
|$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.
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.
|$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|
|$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.
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.