Skip to content →

DMAs

As most systems the SNES also offers to run DMAs, so Direct Memory Access, which are a fast way of copying stuff from one place to another, without a necessary man-in-the-middle. Since we want to run and test our PPU and its function with krom’s tests, we will need to make our DMAs work.

There are also H-DMAs, so DMAs that happen only during horizontal blanking. Those won't be covered here, but in a later chapter, when they will be used to create more advanced image effects.

The system offers 8 individual, bi-directional DMAs. They can be started by writing the appropriate bit to register 0x420B, so bit 6 starts DMA6, bit 2 starts DMA2 and so on.

The registers 0x43x0 are used to configure each DMA individually, where x is the ID of the DMA, so DMA5 is configured on 0x4350 and so on.

The actual bits on these registers are used to set the following settings:

7Transfer Direction (0=A:CPU to B:I/O, 1=B:I/O to A:CPU)
6Addressing Mode (0=Direct Table, 1=Indirect Table) (HDMA only)
5Unused
4-3A-BUS Address Step (0=Increment, 2=Decrement, 1/3=None) (DMA only)
2-0Unit Size & Transfer Format

The Unit Size & Transfer Format defines the size and the pattern of what the DMA will transfer in every step, and has the follwing settings:

000Transfer 1 Bytexxe.g. WRAM
001Transfer 2 Bytesxx, xx+1e.g VRAM
010Transfer 2 Bytesxx, xxe.g. OAM or CGRAM
011Transfer 4 Bytesxx, xx, xx+1, xx+1e.g. BGnxOFS
100Transfer 4 Bytesxx, xx+1, xx+2, xx+3e.g. BGnSC
101Transfer 4 Bytesxx, xx+1, xx, xx+1Reserved
110Transfer 2 Bytesxx, xxReserved, same as Mode 2
111Transfer 4 Bytesxx, xx, xx+1, xx+1Reserved, same as Mode 3

The additional registers to set up the DMA are the following:

0x43x1Address of the B-Bus (e.g. VRAM Write is 0x2118, so 0x18)
0x43x2Lower Byte of A-Bus Address (CPU address space)
0x43x3Upper Byte of A-Bus Address (CPU address space)
0x43x4Bank Byte of A-Bus Address (CPU address space)
0x43x5Lower Byte of Number of Bytes to Transfer
0x43x6Upper Byte of Number of Bytes to Transfer

So for example, if we wanted to transfer 0x800 bytes from 0x13BE00 to VRAM, the completely set up DMA would look like this (and would only need to be started by writing bit 0 of 0x420B)

Example DMA 0x800 bytes from 0x13BE00 to VRAM

The final function that is triggered by the writes to 0x420B could then look something like this:

void startDMA() {
	for (u8 i = 0; i < 8; i++) {
		if ((memory[0x420b] & (1 << i)) == (1 << i)) {
			u8 _IO = memory[0x4301 + (i * 0x10)];
			u8 _v = memory[0x4300 + (i * 0x10)];
			u8 dma_dir = _v >> 7;				//	0 - write to IO, 1 - read from IO
			u8 dma_step = (_v >> 3) & 0b11;		//	0 - increment, 2 - decrement, 1/3 = none
			u8 dma_mode = (_v & 0b111);
			u32 target_adr = (memory[0x4304 + (i * 0x10)] << 16) | (memory[0x4303 + (i * 0x10)] << 8) | memory[0x4302 + (i * 0x10)];
			//	count bytes
			u16 c = (memory[0x4306 + (i * 0x10)] << 8) | memory[0x4305 + (i * 0x10)];
			switch (dma_mode) {
			case 0: {				//	transfer 1 byte (e.g. WRAM)
				while (((memory[0x4306 + (i * 0x10)] << 8) | memory[0x4305 + (i * 0x10)]) > 0) {		//	read bytes count
					if (!dma_dir)
						writeToMem(memory[target_adr], 0x2100 + _IO);
					else
						writeToMem(readFromMem(0x2100 + _IO), target_adr);
					c--;
					memory[0x4306 + (i * 0x10)] = c >> 8;
					memory[0x4305 + (i * 0x10)] = c & 0xff;
					target_adr += (dma_step == 0) ? 1 : ((dma_step == 2) ? -1 : 0);
				}
				break;
			}
			case 1:					//	transfer 2 bytes (xx, xx + 1) (e.g. VRAM)
				while (((memory[0x4306 + (i * 0x10)] << 8) | memory[0x4305 + (i * 0x10)]) > 0) {		//	read bytes count
					if (!dma_dir) {
						writeToMem(memory[target_adr], 0x2100 + _IO);
						if (!--c) return;
						writeToMem(memory[target_adr + 1], 0x2100 + _IO + 1);
						if (!--c) return;
					}
					else {
						writeToMem(readFromMem(0x2100 + _IO), target_adr);
						if (!--c) return;
						writeToMem(readFromMem(0x2100 + _IO + 1), target_adr + 1);
						if (!--c) return;
					}
					memory[0x4306 + (i * 0x10)] = c >> 8;
					memory[0x4305 + (i * 0x10)] = c & 0xff;
					target_adr += (dma_step == 0) ? 2 : ((dma_step == 2) ? -2 : 0);
				}
				break;

...

Comments

Leave a Reply