Sega Mega Drive/DMA

From Sega Retro

DMA (Direct Memory Access) is a method for transfering data from the 68k RAM or ROM to VRAM, CRAM or VSRAM without needing to write code to send it via the data port. It can also be used to fill VRAM with a specified value, or perform a VRAM to VRAM copy.

DMA is generally faster than the equivalent 68k code, though during transfer the 68k CPU is frozen. For this reason, it is recommended only to use DMA during VBlank. The 68k is not frozen during VRAM fill and VRAM to VRAM copy operations, and the Z80 CPU will run as normal during any DMA operation unless it attempts to access the bus.

68k to VRAM/CRAM/VSRAM copy

1: Set length and source

Registers $13 and $14 set the DMA length:

7
6
5
4
3
2
1
0

L7-L0
7
6
5
4
3
2
1
0

H7-H0
  • L7-L0: Low byte of DMA length in bytes, divided by 2.
  • H7-H0: High byte of DMA length in bytes, divided by 2.

Registers $15, $16 and $17 set the DMA source (68k address in RAM, ROM or SRAM):

7
6
5
4
3
2
1
0

L7-L0
7
6
5
4
3
2
1
0

M7-M0
7
6
5
4
3
2
1
0

0
H6-H0
  • L7-L0: Low byte of DMA source address, divided by 2.
  • M7-M0: Middle byte of DMA source address, divided by 2.
  • H6-H0: High byte of DMA source address, divided by 2.

The following code will set the appropriate registers:

move.w #$8F02,($c00004).l
move.w #$9300+(($xxxx>>1)&$FF),($c00004).l
move.w #$9400+((($xxxx>>1)&$FF00)>>8),($c00004).l
move.w #$9500+(($yyyyyy>>1)&$FF),($c00004).l
move.w #$9600+((($yyyyyy>>1)&$FF00)>>8),($c00004).l
move.w #$9700+((($yyyyyy>>1)&$7F0000)>>16),($c00004).l

N.B. The source data should all be located within the same 128kB ($20000) section of ROM. For example, attempting to read $400 bytes from the address $1FFE00 will result in half the data being read from the wrong location, because it overflows the first 128kB section. Section boundaries are located at $20000, $40000, $60000, $80000, $A0000 and so on.

2: Set destination

Sending the following longword to the control port will both set the target VRAM/CRAM/VSRAM address and trigger the DMA:

7
6
5
4
3
2
1
0

CD1
1
A13-A8
7
6
5
4
3
2
1
0

A7-A0
7
6
5
4
3
2
1
0

0
0
0
0
0
0
0
0
7
6
5
4
3
2
1
0

1
0
0
CD2
0
0
A15
A14
  • A15-A0: VRAM/CRAM/VSRAM address.
  • CD2-CD1: 00 = VRAM; 01 = CRAM; 10 = VSRAM.
Memory space CD2-CD1 Code
VRAM 00 move.l #$40000080+(($zzzz&$3FFF)<<16)+(($zzzz&$C000)>>14),($C00004).l
CRAM 01 move.l #$C0000080+($zz<<16),($C00004).l
VSRAM 10 move.l #$40000090+($zz<<16),($C00004).l

VRAM to VRAM copy

1: Set length and source

Registers $13 and $14 set the DMA length:

7
6
5
4
3
2
1
0

L7-L0
7
6
5
4
3
2
1
0

H7-H0
  • L7-L0: Low byte of DMA length in bytes, divided by 2.
  • H7-H0: High byte of DMA length in bytes, divided by 2.

Registers $15, $16 and $17 set the DMA source (VRAM address):

7
6
5
4
3
2
1
0

L7-L0
7
6
5
4
3
2
1
0

H7-H0
7
6
5
4
3
2
1
0

1
1
0
0
0
0
0
0
  • L7-L0: Low byte of DMA source address.
  • H7-H0: High byte of DMA source address.

The following code will set the appropriate registers:

move.w #$8F01,($c00004).l
move.w #$9300+(($xxxx>>1)&$FF),($c00004).l
move.w #$9400+((($xxxx>>1)&$FF00)>>8),($c00004).l
move.w #$9500+($yyyy&$FF),($c00004).l
move.w #$9600+(($yyyy&$FF00)>>8),($c00004).l
move.w #$97C0,($c00004).l

2: Set destination

Sending the following longword to the control port will both set the target VRAM address and trigger the DMA:

7
6
5
4
3
2
1
0

0
0
A13-A8
7
6
5
4
3
2
1
0

A7-A0
7
6
5
4
3
2
1
0

0
0
0
0
0
0
0
0
7
6
5
4
3
2
1
0

1
1
0
0
0
0
A15
A14
  • A15-A0: VRAM address.

The following code will set the target VRAM address and trigger the DMA:

move.l #$000000C0+(($zzzz&$3FFF)<<16)+(($zzzz&$C000)>>14),($C00004).l

VRAM fill

1: Set length

Registers $13 and $14 set the DMA length:

7
6
5
4
3
2
1
0

L7-L0
7
6
5
4
3
2
1
0

H7-H0
  • L7-L0: Low byte of DMA length in bytes, divided by 2.
  • H7-H0: High byte of DMA length in bytes, divided by 2.

The following code will set the appropriate registers:

move.w #$8F01,($c00004).l
move.w #$9300+(($xxxx>>1)&$FF),($c00004).l
move.w #$9400+((($xxxx>>1)&$FF00)>>8),($c00004).l
move.w #$9780,($c00004).l

2: Set destination

Sending the following longword to the control port will set the target VRAM address:

7
6
5
4
3
2
1
0

0
0
A13-A8
7
6
5
4
3
2
1
0

A7-A0
7
6
5
4
3
2
1
0

0
0
0
0
0
0
0
0
7
6
5
4
3
2
1
0

1
1
0
0
0
0
A15
A14
  • A15-A0: VRAM address.

The following code will set the target VRAM address:

move.l #$40000080+(($zzzz&$3FFF)<<16)+(($zzzz&$C000)>>14),($C00004).l

3: Set fill value

Sending the following word to the data port will set the fill value:

7
6
5
4
3
2
1
0

F7-F0
7
6
5
4
3
2
1
0

0
0
0
0
0
0
0
0
  • F7-F0: Fill value.

The following code will set the fill value and trigger the DMA:

move.w #$yy00,($c00000).l

Bugs and peculiarities

  • If 68k to VRAM length is set to 0, it will interpret this as a length of 65,536 words instead (exceeding the capacity of the VRAM and causing an overflow). This is because the VDP decrements the length counter before checking if it's reached zero. 68k to CRAM or VSRAM copies won't overflow in the same way because they are automatically stopped after copying to the last available byte.
  • As mentioned above, source data must all sit within the same 128kB section of ROM. Since the high byte is not incremented, DMA will read from the start of the 128kB section when it overflows.

Bandwidth

The following values are the maximum number of bytes that can be written by DMA in a single frame, either during the frame (not recommended) or between frames (in VBlank). The values most likely to be useful are in bold.

Type Resolution During VBlank During active display Total
68k to VRAM/CRAM/VSRAM copy 60 Hz 256 x 224 6118 ($17E6) 3584 9702
320 x 224 7524 ($1D64) 4032 11556
50 Hz 256 x 224 14329 3584 17913
320 x 224 17622 4032 21654
256 x 240 11753 3840 15593
320 x 240 14454 4320 18774
VRAM to VRAM copy 60 Hz 256 x 224 6308 ($18A4) 3360 9668
320 x 224 7752 ($1E48) 3808 11560
50 Hz 256 x 224 14774 3360 18134
320 x 224 18156 3360 18134
256 x 240 12118 3600 15718
320 x 240 14892 4080 18972
VRAM fill 60 Hz 256 x 224 3154 ($C52) 1792 4946
320 x 224 3876 ($F24) 2016 5892
50 Hz 256 x 224 7387 1792 9179
320 x 224 9078 2016 11094
256 x 240 6059 1920 7979
320 x 240 7446 2160 9606

Summary

Type Variables Code
68k to VRAM/CRAM/VSRAM copy
  • $xxxx = length in bytes
  • $yyyyyy = source address
  • $zzzz = target address
move.w #$8F02,($c00004).l
move.w #$9300+(($xxxx>>1)&$FF),($c00004).l
move.w #$9400+((($xxxx>>1)&$FF00)>>8),($c00004).l
move.w #$9500+(($yyyyyy>>1)&$FF),($c00004).l
move.w #$9600+((($yyyyyy>>1)&$FF00)>>8),($c00004).l
move.w #$9700+((($yyyyyy>>1)&$7F0000)>>16),($c00004).l

to VRAM:

move.l #$40000080+(($zzzz&$3FFF)<<16)+(($zzzz&$C000)>>14),($C00004).l

to CRAM:

move.l #$C0000080+($zz<<16),($C00004).l

to VSRAM:

move.l #$40000090+($zz<<16),($C00004).l
VRAM to VRAM copy
  • $xxxx = length in bytes
  • $yyyy = source address
  • $zzzz = target address
move.w #$8F01,($c00004).l
move.w #$9300+(($xxxx>>1)&$FF),($c00004).l
move.w #$9400+((($xxxx>>1)&$FF00)>>8),($c00004).l
move.w #$9500+($yyyy&$FF),($c00004).l
move.w #$9600+(($yyyy&$FF00)>>8),($c00004).l
move.w #$97C0,($c00004).l
move.l #$000000C0+(($zzzz&$3FFF)<<16)+(($zzzz&$C000)>>14),($C00004).l
VRAM fill
  • $xxxx = length in bytes
  • $yy = value to fill with
  • $zzzz = target address
move.w #$8F01,($c00004).l
move.w #$9300+(($xxxx>>1)&$FF),($c00004).l
move.w #$9400+((($xxxx>>1)&$FF00)>>8),($c00004).l
move.w #$9780,($c00004).l
move.l #$40000080+(($zzzz&$3FFF)<<16)+(($zzzz&$C000)>>14),($C00004).l
move.w #$yy00,($c00000).l