The VDP control port is a read/write port allowing the VDP registers to be written; the VRAM/CRAM read/write address to be set; and the status flags to be read. The control port address is $BF (used in outa/ina instructions).


Writes to the VDP control port consist of two 8-bit bytes to form a 16-bit control word. The lower eight bits are written first.

In order to know whether any particular byte is the first or second, an internal flag is set after the first is written which identifies the next byte as a "second byte". When the control port is read, or the data port is read or written, the flag is cleared, removing most cases of ambiguity; nevertheless, interrupt routines can cause problems and thus interrupts should be disabled during important VDP accesses.

Data format

The VDP has two internal registers: a 2-bit "code" register determining the operational mode, and a 14-bit "address" register specifying an address (in most cases). The 16 bits written are as follows:

BytesSecond byteFirst byte

Software may choose to only write the "first byte" to update only the lower eight bits of the address register, but any actions triggered by the second byte write will not be performed in this case.

The meaning of the value in the code register is as follows:

codeaddress is
0 (%00)VRAM read address
1 (%01)VRAM write address
2 (%10)Register write
3 (%11)CRAM write address

VRAM read address

When the second byte is written, the value at the VRAM location specified by the address register is retrieved and stored in a buffer, and the address register is incremented; if it exceeds $3fff, it wraps to $0000.

Any subsequent data port read will return the value in the buffer. The value stored at the current VRAM location specified by the address register is then copied to the buffer, and the address register is incremented. Thus, the buffer allows the VDP to respond more quickly than it otherwise could.

A data port write will write to VRAM at the current address, copy the same value to the buffer, and increment the address register. Because the address register is incremented when entering this mode, it is easy to make mistakes when doing this; thus, writing when in read mode is not recommended.

VRAM write address

Any subsequent data port read or write acts exactly as in the previous case.

Because the address register is not incremented, this means the first byte read will come from the address specified.

Note that the first byte read will return the contents of the read buffer - not the contents of the specified VRAM address. The data read will generally be lagged by one byte because of the way the read buffer works. Reading in write mode is useful, however, for skipping bytes (incrementing the address register without writing).

Register write

A VDP register is updated with the current address register value:

FunctionUnusedRegister numberData

Any subsequent data port reads or writes will act as in the previous cases, interpreting the address register value as a VRAM address. This is unlikely to be useful, and is thus not recommended.

CRAM write address

Any subsequent data port read will read from VRAM as above. However, a write will write to CRAM (palette RAM) at the current address. If the address register exceeds the CRAM size (32 or 64 bytes), the high bits are ignored so it will always address CRAM; for example, address $1000 wil read from CRAM address $00.


Reads from the control port return a status byte:

MeaningVBlankSprite overflowSprite collisionFifth sprite

All of these correspond to internal VDP flags, and all of these flags are reset when the status byte is read. It is therefore important to handle all of the flags you are interested in, each time the status byte is read.

It is also worth noting that if you disable interrupts on the Z80 (using the di instruction), you can still detect VBlank interrupts by polling the status byte.

Return to top