From Sonic Retro
|SCHG: VDP Documentation|
This article is a work in progress.
The addresses for the VDP ports are shown exhaustively below, though only $C00000 and $C00004 are used generally.
|$C00002||B/W||Data port (mirror)|
|$C00006||B/W||Control port (mirror)|
|$C00008||B/W||HV counter (read-only)|
|$C0000A||B/W||HV counter (read-only, mirror)|
|$C0000C||B/W||HV counter (read-only, mirror)|
|$C0000E||B/W||HV counter (read-only, mirror)|
|$C00011||B||SN76489 PSG (write-only)|
|$C00013||B||SN76489 PSG (write-only, mirror)|
|$C00015||B||SN76489 PSG (write-only, mirror)|
|$C00017||B||SN76489 PSG (write-only, mirror)|
|$C0001C||W||Unused mystery port|
The control port, as the name suggests, allows the 68000 to control VDP memory access. Additionally, it also allows the reading of VDP status and the setting of registers. The control port is accessed at address $C00004 from the 68000.
Getting VDP status
Reading the control port returns a VDP status word, which has the following format, counting from the least significant bit:
|0||This is 0 if the VDP is in NTSC mode, and 1 if it's in PAL mode.|
|1||This is set for the duration of a DMA operation. It is only useful for fills and copies, as the 68000 is frozen for transfers from 68000 memory to VDP.|
|2||Set during horizontal blanking.|
|3||Set during vertical blanking.|
|4||Set when the odd frame is being displayed in interlaced mode, clear otherwise.|
|5||Set when any two sprites have non-transparent pixels overlapping each other. Its usefulness is quite limited however since it cannot be determined which two sprites collided and where.|
|6||Set when there are too many sprites on a scan line i.e. over 17 in 32-cell mode or over 21 in 40-cell mode.|
|7||Set when a vertical interrupt occurs, presumably cleared at the end of a frame.|
|8||FIFO full flag. The VDP has a FIFO which can hold up to 4 words while the VDP's busy and unable to immediately process the write command. This flag is set when the 4th word is written and the FIFO cannot hold any more data. If the 68000 attempts to write again it will be frozen until at least one of the stored words has been delivered to its destination.|
|9||FIFO empty flag. This flag is only set when the FIFO is completely empty. If the FIFO is only partially filled both this flag and the FIFO full flag are 0.|
|10||Unused (always set).|
|11||Unused (always cleared).|
|12||Unused (always set).|
|13||Unused (always set).|
|14||Unused (always cleared).|
|15||Unused (always cleared).|
Writing to registers
The VDP has 24 write-only registers, each taking bytes, the purpose of which is explained here. A register can be set by writing a word to the control port, which has the following format:
Bits 15 and 14 are always 1 and 0 respectively to differentiate register writes from access control writes. RS4-RS0 is the register number, from $00 to $17 (writing to registers $18-$1F has no effect), and D7-D0 is the data to write. Since the VDP treats longword access as equivalent to two word accesses, two registers can be programmed at the same time by writing a longword:
move.l #$80048134,($C00004).l ; set register 0 to $04 and register 1 to $34
Setting VDP access
The 68000 can set VDP access by writing a longword to the control port, which has the following format:
A15-A0 is the destination RAM address. CD5 is the DMA flag, and CD4 is set for a VRAM copy DMA operation. Bits CD3-CD0 govern the type of access:
After VDP access has been set, data can be read or written through the data port, which is accessed at address $C00000 from the 68000. After each read or write, the address is increased by the value of the auto increment register (register $0F). Attempting to read data after setting up a write operation or vice-versa will cause the read or write to be ignored.
As an example, the following code swaps first four colours of the first palette line (CRAM addresses 0 to 7) with the first four colours of the last palette line (CRAM addresses $60 to $67):
lea ($C00004).l,a5 ; VDP control port lea ($C00000).l,a6 ; VDP data port move.w #$8F02,(a5) ; set auto-increment to 2 (see registers page for explanation) move.l #$00000020,(a5) ; CRAM read at address $0 move.l (a6),d0 ; get first two colours of first palette line move.l (a6),d1 ; get next two colours of first palette line move.l #$00600020,(a5) ; CRAM read at address $60 move.l (a6),d2 ; get first two colours of last palette line move.l (a6),d3 ; get next two colours of last palette line move.l #$C0000000,(a5) ; CRAM write at address $0 move.l d2,(a6) ; set first two colours of first palette line move.l d3,(a6) ; set next two colours of first palette line move.l #$C0600000,(a5) ; CRAM write at address $60 move.l d0,(a6) ; set first two colours of last palette line move.l d1,(a6) ; set next two colours of last palette line
The H/V counter is a read-only port which can be accessed at address $C00008 from the 68000. It keeps track of the position of the TV beam and its value is only meaningful in active scan (i.e. not during vertical blank).
Reading the port will lead to a 2 byte value: the low byte is the horizontal position of the beam and the high byte is the vertical position. Under H40 or interlaced modes, values may be bigger than 255 and hence don't fit. Because of this, the LSB of the vertical counter is replaced by the MSB in interlaced mode, and the LSB of the horizontal counter is always dropped (so it'll have half the real value).