The Mega Drive’s video chip (VDP) draws two scrolling planes, a window and sprites from video RAM, and is programmed by writing to two ports. This page lists the ports, the command words, the status register and the 24 registers as a game uses them. It follows Plutiedev’s VDP register and command references and Genesis Plus GX’s source for the status bits; Sega’s manuals were not read. It does not describe how the chip draws (planes, sprites, priorities, shadow and highlight), only the programming interface.
Ports
The ports are at $C00000–$C0001F, mirrored (see the memory map). On a console with the
TMSS they are unusable until SEGA has been written to $A14000.
| Address | Port | Use |
|---|---|---|
$C00000 | Data port | Read or write video memory words (or long words, seen as two words) at the address set by a command. After each word the address advances by the auto-increment (register $0F). |
$C00004 | Control port | Write a command (below); read the status register. |
$C00008 | HV counter | Read the horizontal and vertical beam position (see below). |
$C00011 | PSG | The sound chip, written as a byte (it shares this range). |
Command words
Everything is written to the control port as one 16-bit word or two. Plutiedev lists three kinds.
Set a register
Write the word $8000 | (register << 8) | value. Setting a register cancels the current video address, so set the address again before more reads or writes.
Set a video address
Write a 32-bit value (a long word or two words): ((address & $3FFF) << 16) | ((address & $C000) >> 14) | command.
With the 128 KiB VRAM mode that some hardware has (the Tera Drive), address bits 16–14 go in bits 2–0 of the low word. The command is:
| Memory | Write | Read | DMA destination |
|---|---|---|---|
| VRAM | $40000000 | $00000000 | $40000080 |
| CRAM (colours) | $C0000000 | $00000020 | $C0000080 |
| VSRAM (vertical scroll) | $40000010 | $00000010 | $40000090 |
After a write command, words written to the data port go to that memory; after a read command, words read from it come back.
Start a DMA
The DMA destination commands above are the write commands with bit 7 of the second word set. The type is chosen by register $17: for a transfer from the 68000’s memory or a copy,
the operation starts as soon as the destination is written; for a fill, the fill value must then be written to the data port. A length of 0 in registers $13/$14 means 65536,
and a transfer cannot cross a 128 KiB boundary of its source (it wraps back to the start of that 128 KiB block) (Plutiedev, “Hardware issues”).
Status register (read the control port)
Reading $C00004 returns the status word. The bits, as Genesis Plus GX documents and uses them (the meaning of the bits was not checked against a Sega document):
| Bit | Meaning |
|---|---|
| 0 | 0 = NTSC, 1 = PAL |
| 1 | DMA busy |
| 2 | In horizontal blank |
| 3 | In vertical blank |
| 4 | Odd field (in the interlaced modes; 0 even, 1 odd) |
| 5 | Sprite collision (cleared by reading) |
| 6 | Too many sprites on a line (cleared by reading) |
| 7 | A vertical interrupt has occurred |
| 8 | Write FIFO full |
| 9 | Write FIFO empty |
| 10–15 | Open bus (whatever the 68000 last had on its bus) |
HV counter
Reading $C00008 (or its mirrors) returns the position of the beam: the vertical counter in the high byte and the horizontal counter in the low byte. Register $00 bit 1 freezes it
(M3), which is how a light gun’s program latches a position. How each counter counts in each video mode (the values that repeat, the jump in the middle of a line) is not described on this page or by
the sources read for it.
Registers
There are 24 registers, $00–$17, each written as $80xx–$97xx. The registers $06, $08, $09 and $0E are not in Plutiedev’s list; they
are shown as unused in the viewer. Bit names are Plutiedev’s.
| Reg. | Write | Name | Bits 7 to 0 and meaning |
|---|---|---|---|
$00 | $80xx | Mode set 1 | 0 0 LCB IE1 0 1 M3 0. LCB: 1 blanks the leftmost 8-pixel column. IE1: 1 enables the horizontal-blank interrupt. M3: 1 freezes the HV counter. |
$01 | $81xx | Mode set 2 | 0 DISP IE0 M1 M2 1 0 0. DISP: 1 enables rendering. IE0: 1 enables the vertical-blank interrupt. M1: 1 allows DMA. M2: vertical size, 0 = 28 tiles (V28), 1 = 30 tiles (V30; works properly on PAL only). |
$02 | $82xx | Plane A table address | 0 0 SA15 SA14 SA13 0 0 0: base address divided by $2000. |
$03 | $83xx | Window table address | 0 0 WD15 WD14 WD13 WD12 WD11 0: base address divided by $800; in H40 mode WD11 must be 0. |
$04 | $84xx | Plane B table address | 0 0 0 0 0 SB15 SB14 SB13: base address divided by $2000. |
$05 | $85xx | Sprite table address | 0 AT15 AT14 AT13 AT12 AT11 AT10 AT9: base address divided by $200; in H40 mode AT9 must be 0. |
$07 | $87xx | Background colour | 0 0 CPT1 CPT0 COL3 COL2 COL1 COL0: the palette (CPT) and the colour index in it (COL). |
$0A | $8Axx | H-interrupt rate | HIT7 … HIT0: how many lines to wait between horizontal-blank interrupts. |
$0B | $8Bxx | Mode set 3 | 0 0 0 0 IE2 VSCR HSCR LSCR. IE2: 1 enables the external interrupt. VSCR: 0 full-screen vertical scroll, 1 scroll every two tiles. HSCR/LSCR: 00 full-screen horizontal scroll, 01 eight lines then repeat, 10 every tile, 11 every line. |
$0C | $8Cxx | Mode set 4 | RS0 0 0 0 S/TE LSM1 LSM0 RS1. RS0 and RS1 (both 0: 32 tiles wide, H32; both 1: 40 tiles, H40). S/TE: 1 enables shadow/highlight. LSM1–0: 00 no interlace, 01 interlaced without a resolution increase, 11 interlaced with doubled vertical resolution. |
$0D | $8Dxx | H-scroll table address | 0 0 HS15 HS14 HS13 HS12 HS11 HS10: base address divided by $400. |
$0F | $8Fxx | Auto-increment | INC7 … INC0: bytes added to the video address after each data-port access. |
$10 | $90xx | Tile-map size | 0 0 VSZ1 VSZ0 0 0 HSZ1 HSZ0: size in tiles (vertical, horizontal) of the planes: 00 00 32×32, 00 01 64×32, 00 11 128×32, 01 00 32×64, 01 01 64×64, 11 00 32×128. Other combinations do not work as expected. |
$11 | $91xx | Window X split | RIGT 0 0 WHP5 WHP4 WHP3 WHP2 WHP1. RIGT: 0 = the window is to the left of the split, 1 = to the right. WHP: the split’s X position in 16-pixel steps. |
$12 | $92xx | Window Y split | DOWN 0 0 WVP4 WVP3 WVP2 WVP1 WVP0. DOWN: 0 = above the split, 1 = below. WVP: the split’s Y position in 8-pixel steps. |
$13 | $93xx | DMA length, low | LG7 … LG0. |
$14 | $94xx | DMA length, high | LG15 … LG8. A length of 0 means 65536. |
$15 | $95xx | DMA source, low | SA8 … SA1 (source address in words). |
$16 | $96xx | DMA source, middle | SA16 … SA9. |
$17 | $97xx | DMA source, high and type | DMD1 DMD0 SA22 … SA17. Type: 0x a transfer from memory (DMD0 then becomes SA23), 10 a VRAM fill, 11 a VRAM copy. |
The video memories
The VDP has its own video RAM (VRAM, 64 KiB on a standard console; Plutiedev mentions a 128 KiB mode on the Tera Drive), colour RAM (CRAM) and vertical-scroll RAM (VSRAM). Plutiedev notes that VSRAM is 40 words long on most revisions and 64 words from model 2 VA4 on. Sizes of CRAM and the details of tile, name-table and sprite formats are not described on this page.
Sources
- VDP register reference – Plutiedev: every register above, with its bits (read in full)
- VDP command reference – Plutiedev: set register, set write or read address, start DMA, and the command values
- Memory map – Plutiedev: the port addresses and mirrors
- Hardware issues – Plutiedev: DMA with length 0, the 128 KiB boundary, the VSRAM size, V30 on PAL
- vdp_ctrl.c – Genesis Plus GX: the status register’s bits