assemblydmamotorola68000

Sega Genesis DMA moves my sprite around the screen


EDIT: It wasn't the DMA doing that after all, it was the fact that the routine clobbered my other registers. Silly me.

So I'm trying to understand how DMA (Direct Memory Access) works on the Sega Genesis, and I've gotten the VRAM Fill mode to work mostly the way I would expect, however, there's one little problem. Doing it seems to shift my game sprite around the screen and I don't understand why. For reference, here is a picture of my game running in Fusion:

enter image description here

For the necessary background info, the puppy dog is a sprite and all other graphics are tiles, with the boxes, the lime, and the red squares as foreground tiles, and the clouds and sky are background tiles. The window layer is off-screen. I'm using the VRAM configuration where the foreground layer is at VDP address $C000 (sega code $40000003), sprite attribute table at $D800 ($58000003) background at $E000($60000003), and window at $F000 ($7000003). I've set things up to where I can press C on the controller to start DMA. Here's the routine that executes the DMA command.

dma_fill:
    ;explanation of macros and defined constants:
    ;pushRegs = MOVEM.L ___,-(SP)
    ;popRegs  = MOVEM.L (SP)+,____
    ;DI = MOVE #$2300,SR
    ;pushf = MOVE SR,-(SP)
    ;popf  = MOVE (SP)+,SR
    ;CD5 = %10000000
    ;VDP_CTRL = $00C00004
    ;VDP_DATA = $00C00000

    ;input:
    ;D2.L = what address to write to.
    ;D1.W = DMA LENGTH
    ;D0 = WHAT DATA TO USE TO FILL VRAM
    pushRegs D3-D7
    pushf
        DI      ;we don't want interrupts during this time.

        MOVEQ.L #-109,D3            ;quickly move #$FFFFFF93 into D3
        LSL.W #8,D3
        OR.B D1,D3                  
        ;d3 contains $93xx where xx is the low byte of dma length
        ;this is the correct command to give the vdp
        
        
        LSR.W #8,D1 ;shift high byte of dma length down to low byte
        
        MOVEQ.L #-108,D4            ;quickly move #$FFFFFF94 into d4
        LSL.W #8,D4                 ;D4 = #$FFFF9400
        OR.B D1,D4
        ;d3 contains $94xx where xx is the high byte of dma length
        ;this is the correct command to give the vdp
        OR.L #CD5,D2        ;tells the vdp the next write is a DMA write
        
.wait:
        move.w VDP_ctrl,d7
        and.w #%0000000000001000,d7     ;See if vblank is running
        bne .wait                       ;wait until it is
        
        MOVE.W #($8100|%01110100),(VDP_CTRL)    ;ENABLE DMA
        move.w #$8F01,(vdp_ctrl)                ;set auto-inc to 1
        MOVE.W #$9780,(vdp_ctrl)                ;enable dma vram fill
        ; HALT
        MOVE.W D3,(vdp_ctrl)                    ;set dma length low byte
        MOVE.W D4,(vdp_ctrl)                    ;set dma length high byte
        MOVE.L D2,(vdp_ctrl)                    ;set destination address
        
        MOVE.W D0,(vdp_data)                    ;write the data, dma begins here.
        ;do I need to wait for DMA to finish before continuing?
; .waitDma:
        ; MOVE.W (vdp_ctrl),d6
        ; btst #1,d6
        ; bne .waitDma
        
    
        move.w #($8100|%01100100),(VDP_CTRL)    ;DISABLE DMA
        move.w #$8F02,(vdp_ctrl)                ;set auto-inc back to 2
    popf        ;restore flags and interrupt level
    popRegs D3-D7
    RTS

With the parameters of a fill value of 0 (the tile number of an empty 8x8 tile) in D0, a fill length of $0400 in D1, and a destination of $40000003 (the foreground tilemap), I would expect the VRAM region of $C000-$C400 to be filled with blank tiles. But here's what actually happens.

enter image description here

About the first 4 rows of metatiles get cleared (keep in mind that in my game each "metatile" is four 8x8 tiles.), which seems about right as far as the length is concerned. But the dog sprite has changed position. Given that I have the sprite attribute table at VDP address $D800, this really doesn't make sense. Out of curiosity, I've tried the same function with the length parameter changed to $0200, and when I do that, none of the tiles get cleared but the sprite is still moved to the same location on screen. How bizarre. The existing documentation I could find was either not very well translated into English, or doesn't really explain all the details on what actually happens in memory (it focuses more on HOW to make DMA occur, which wasn't too hard to figure out, but I don't understand why my sprites move as a result.)


Solution

  • The real problem was code that I hadn't shown, and my registers were getting clobbered which caused the sprites to move. It wasn't the DMA that was the problem.