Page 1 of 1

First Steps with DMA

Posted: Fri Jan 25, 2013 9:44 pm
by Zontar
This is the first time I'm attempting to write a game in straight 68k assembly (using ASM68k), having used SGDK in the past. So far, I've succeeded in loading a palette into CRAM; now I am attempting to use DMA to transfer an 8x8 tile into the first area of VRAM. But the correct way to do this eludes me.

This is the tile I'm attempting to load into VRAM:

Code: Select all

A:
	dc.l    $00077000
	dc.l    $07700770
	dc.l    $07700770
	dc.l    $07777770
	dc.l    $07700770
	dc.l    $07700770
	dc.l    $00000000
	dc.l    $00000000
Here is the code I have to initialize the VDP and prepare it for DMA. This is information I got out of the Genesis/Megadrive Software Manual. For clarity, I'll explain every line so you don't have to sit and figure out the hex.

Code: Select all

	move.w #$8F02, ($C00004)  ; VDP Register #15, Increment by 2
	move.w #$8174, ($C00004)  ; VDP Register #1, Enable DMA
	move.w #$9310, ($C00004)  ; VDP Register #19, Transfer 16 words
	move.w #$9400, ($C00004)  ; VDP Register #20, Transfer 16 words (upper bytes 0, don't need greater than $FF words)
	lea A, a0				  ; Load the location of the A tile
	move.w #$9500, d0		  ; VDP Register #21, low byte
	add a0, d0
	move.w d0, ($C00004)
	;;; THIS IS THE TROUBLE SPOT, MED AND HIGH BYTES
	move.l #$40000080, ($C00004) ; Begin DMA Transfer
	; << 68k Frozen?? >>
	move.w #$8164, ($C00004)     ; End DMA Transfer
As you can see, I'm attempting to add the low byte of a0 to $9500, so I can poke the low byte of the tile address into register #21. Being somewhat new to 68k assembly, I'm not sure if I'm missing some kind of addressing mode or something I can be using here, but I cannot for the life of me figure out how to segment the location of the A tile data. My original plan was to use the "LSR" instruction to give me access to the next byte to load into registers #22 and #23.

Upon assembly, and consultation of my 68k Quick Reference, "LSR" cannot be used on address registers, and "lsr #8,a0" was therefore not doable. So, I'm not immediately sure how to gain access to specific bytes in an address register word/longword.

If that's what I need to even do. In fact, I'm not even certain if any of this is correct. The Genesis programmer's manual gives me a warning about "occasional DMA failure" when transferring ROM->VRAM so does it have to go into RAM first?

In short, I really don't know what I'm doing. That's my shot at it. If someone has an example of DMA being done, or can tell me how I can shift an address register, I would appreciate that.

Posted: Sat Jan 26, 2013 12:10 am
by TmEE co.(TM)

Code: Select all

DoDMAtoVRAM:            ; Does DMA to VRAM
 MOVE.L #CPORT, A1      ; D0 = WORDs to transfer
 MOVE.L #$94009300, D1  ; D2 = VRAM address
 OR.B   D0, D1          ; A0 = source address
 LSR.W  #8, D0
 SWAP   D0
 CLR.W  D0
 OR.L   D0, D1
 MOVE.L D1, (A1)
 MOVE.L A0, D0
 LSR.L  #1, D0
 MOVE.L #$97009500, D1
 OR.B   D0, D1
 LSR.W  #8, D0
 OR.W   #$9600, D0
 MOVE.W D0, (A1)        ; MID
 SWAP   D0
 SWAP   D1
 AND.B  #$3F, D0
 OR.B   D0, D1
 MOVE.L D1, (A1)        ; LOW, HIGH
 OR.B   #$80, D2
 MOVE.L D2, (A1)
 RTS
This is what I have done in past.

Posted: Sat Jan 26, 2013 6:31 am
by Zontar
All right. That definitely gets me more than where I've been. However, I appear to be misunderstanding the way VDP ram is addressed, as I can't seem to move the "A" out of 0x0000.

As an example, I used your subroutine passing it the following parameters in this code:

Code: Select all

	move.w #$8174, ($C00004)  ; VDP Register #1, Enable DMA
	move.b #$10, d0 ; $10 words (16)
	move.l #$40000080, d2 ; destination address
	lea A, a0 ; source address
	bsr DoDMAtoVRAM
	move.w #$8164, ($C00004)  ; VDP Register #1, Disable DMA
Which successfully gets me the "A" loaded in 0x0000 like so:



As this location is reserved for "transparency", I have been attempting to load my VDP data beginning in the next segment instead. Now according to the software manual, the upper word contains CD1, CD0, and DA13 through DA0, while the lower word consists of 1000 00, and then DA15 and 14.

My reasoning was that, according to this, $40000080 was referring to VRAM 0x0000. So I figured I would try adding 1 to the DA value to get $40010080. Evidently my reasoning is wrong, because instead of moving the "A" into the next VRAM slot, I get this:



So I guess there's something I don't quite understand about how VRAM is addressed. I've looked at your final OR operation in the subroutine (OR.B #$80, D2) in hopes I could get a better idea of what's up here, but it's not immediately evident to me what this bitwise operation's purpose is, or whether or not I have formulated the argument correctly. With that said, what is the proper way to address a particular location in VRAM in the address register?

Posted: Sat Jan 26, 2013 2:33 pm
by TmEE co.(TM)
What is your auto-increment value ? It should be 2
And I shift out the first address bit in that code, to avoid the pixel swaps.

Posted: Sat Jan 26, 2013 5:35 pm
by Zontar
TmEE co.(TM) wrote:What is your auto-increment value ? It should be 2
And I shift out the first address bit in that code, to avoid the pixel swaps.
I had set register #15 to #$02 when loading the CRAM data.

Code: Select all

move.w #$8F02, ($C00004)
I tried moving this line downward, thinking that #2 was already set by default. My CRAM data failed to then load, so I duplicated the line after setting the DMA to "on". Same result as my previous post.

If it's worth anything, I'm loading this directly from ROM without copying the data into RAM first. Is that incorrect?

Posted: Sat Jan 26, 2013 9:08 pm
by HardWareMan
I used this code long time ago.

Code: Select all

# Init
move.w   #$8174,$c00004   ;VDP Setup: Enable+VInt+DMA
move.w   #$8f02,$c00004   ;VDP Increment
move.l   #$C00004,a6      ;VDP Control

#Do DMA
move.w   #$9320,(a6)      ;Low(Size) => #19
move.w   #$940D,(a6)      ;High(Size) => #20
move.w   #$9500,(a6)      ;Low(Org) => #21
move.w   #$96C0,(a6)      ;Mid(Org) => #22
move.w   #$977F,(a6)      ;High(Org) => #23
move.l   #$40000080,(a6)  ;Do VRAM write at 0x0000 with DMA
Sega2.doc says that last 'move' (that trigger DMA) must be in RAM (because some schematic restrictions: ROM reads only by word, but every OPcodes are already aligned by word, or we get AddrErr exception). But I use this code from ROM and do transfer from ROM and RAM with success. Only one important thing must be observed: source data must be aligned by word (because VDP use 16 bit interface). Or you get mess (see Sega2.doc for details).

Posted: Sat Jan 26, 2013 9:59 pm
by Zontar
HardWareMan wrote:I used this code long time ago.

Code: Select all

# Init
move.w   #$8174,$c00004   ;VDP Setup: Enable+VInt+DMA
move.w   #$8f02,$c00004   ;VDP Increment
move.l   #$C00004,a6      ;VDP Control

#Do DMA
move.w   #$9320,(a6)      ;Low(Size) => #19
move.w   #$940D,(a6)      ;High(Size) => #20
move.w   #$9500,(a6)      ;Low(Org) => #21
move.w   #$96C0,(a6)      ;Mid(Org) => #22
move.w   #$977F,(a6)      ;High(Org) => #23
move.l   #$40000080,(a6)  ;Do VRAM write at 0x0000 with DMA
Sega2.doc says that last 'move' (that trigger DMA) must be in RAM (because some schematic restrictions: ROM reads only by word, but every OPcodes are already aligned by word, or we get AddrErr exception). But I use this code from ROM and do transfer from ROM and RAM with success. Only one important thing must be observed: source data must be aligned by word (because VDP use 16 bit interface). Or you get mess (see Sega2.doc for details).
Your code is quite similar to the one I've tried (except using lea to get the address rather than manually specifying it) but for some strange reason, the graphics always corrupt when trying to load outside 0x0000. Most strangely of all, they corrupt *in* 0x0000 when I tell it to load in 0x0001 or 0x0002. All I've done was change it to #$40010080 from #$40000080. I'm using Gens KMod if that's a potential issue (haven't yet found another emulator that shows VDP cells in the debug screen).

I already suspect this is because of the Sega manual warning (although I could still be wrong), but what doesn't make sense is how the destination address must be a single word write when the entire address is spread across two words (the final two bits being in the last nibble of the longword). Is there any real way around that?

Posted: Sat Jan 26, 2013 11:09 pm
by Chilly Willy
Tiles are 32 bytes long, so the next tile is 32, not 1. The first 32 bytes have to all be 0 for a nice clear transparent tile. The next 32 bytes are tile 1, the next 32 are tile 2, and so on.

Posted: Sat Jan 26, 2013 11:49 pm
by Zontar
Chilly Willy wrote:Tiles are 32 bytes long, so the next tile is 32, not 1. The first 32 bytes have to all be 0 for a nice clear transparent tile. The next 32 bytes are tile 1, the next 32 are tile 2, and so on.


That got it! :D That's exactly what I was doing wrong. They're bytes, not cells! What an idiot I am. Thank you so much. Time to explore more of the hardware!