This is a weird one, and I haven't found it documented anywhere. I was testing Sega Genesis DMA during scanline interrupts and found something very odd. When writing to VDP_CTRL
to initiate DMA, if you use the address register indirect to memory addressing mode (MOVE.W (An)+,$addr
), the DMA succeeds only if the address register pointed to RAM! This is the only conclusion I can come to after thorough testing.
For some background info, I created a test ROM where by pressing START the DMA would begin. The goal was to have the palette change colors at a specific scanline, much like Labyrinth Zone in Sonic 1. Pressing START doesn't actually begin the DMA however, it just sets up the VDP parameters in RAM and then sets a flag in RAM that the HBlank IRQ reads to know when to actually start DMA. It's a bit complicated but here goes:
;this executes when pressing start
move.l #Palette_01,d0
LSR.L #1,D0
AND.L #$7FFFFF,D0
lea irq_dma_storage,a0
jsr dmacopy_colors
move.l #Palette_00,d0
LSR.L #1,D0
AND.L #$7FFFFF,D0
lea irq_dma_storage+16,a0
jsr dmacopy_colors
BSET #15,irq_flags
dmacopy_colors:
pushLong a0
MOVE.W #VDP_REG_AUTOINC|2,vdp_ctrl ;set auto-inc to 2
MOVE.W #($8100|%01110100),(A0)+
MOVE.W #VDP_REG_19|($80/2),(a0)+
MOVE.W #VDP_REG_20|0,(A0)+
MOVE.W #VDP_REG_21,D1
OR.B D0,D1
MOVE.W D1,(A0)+
LSR.L #8,D0
MOVE.W #VDP_REG_22,D1
OR.B D0,D1
MOVE.W D1,(A0)+
LSR.L #8,D0
MOVE.W #VDP_REG_23,D1
OR.B D0,D1
AND.B #%00111111,D1 ;memory to vram mode
MOVE.W D1,(A0)+
MOVE.L #$C0000080,(A0)+
poplong a0
RTS
LabyrinthZoneV: ;this runs every vblank
;test moving palette change.
pushlong d0
pushlong a0
add.w #1,frametimer
BTST #14,irq_flags
BEQ .skip
lea irq_dma_storage+16,a0
; LEA PALETTE_00_DMA_CODE,A0
MOVE.L (A0)+,vdp_ctrl
MOVE.L (A0)+,vdp_ctrl
MOVE.L (A0)+,vdp_ctrl
MOVE.L (A0)+,vdp_ctrl ;dma occurs after this line.
BCLR #14,irq_flags
BSET #15,irq_flags
.skip:
MOVE.W #VDP_REG_HINT|180,vdp_ctrl
poplong a0
poplong d0
rte
LabyrinthZoneV_End:
LabyrinthZoneH: ;this runs at scanline #180
;test moving palette change.
pushregs d0-d2/a0
BTST #15,irq_flags
BEQ .exit
LEA irq_dma_storage,A0
; LEA Palette_01_DMA_Code,A0 ;get the same params as above but pre-calculated
MOVE.L #$DEADBEEF,irq_canary
MOVE.L (A0)+,vdp_ctrl
MOVE.L (A0)+,vdp_ctrl
MOVE.L (A0)+,vdp_ctrl
MOVE.L (A0)+,vdp_ctrl ;dma occurs after this line.
MOVE.W #$8164,(VDP_Ctrl) ;disable DMA
BCLR #15,irq_flags
BSET #14,irq_flags
.exit:
popregs d0-d2/a0
rte
LabyrinthZoneH_End:
That all worked as intended. You can see a memory dump of irq_dma_storage
towards the bottom (it begins at the 81 74 underneath BE EF.)
But then I thought, since these parameters are constant anyway, why not just write down these values, store them directly in the ROM, and use those to write to VDP_CTRL
during the interrupts instead?
Palette_00_DMA_Code:
DC.L $81749340
DC.L $94009570
DC.L $96079700
DC.L $C0000080
Palette_01_DMA_Code:
DC.L $81749340
DC.L $940095B8
DC.L $96079700
DC.L $C0000080
;edited snippet from VBLANK irq:
LEA Palette_00_DMA_Code,A0
MOVE.L (A0)+,vdp_ctrl
MOVE.L (A0)+,vdp_ctrl
MOVE.L (A0)+,vdp_ctrl
MOVE.L (A0)+,vdp_ctrl
Well I tried that, and to my surprise, it didn't work. The screen color stayed light blue throughout, as if nothing happened.
And this version is from using a RAM source for the parameters in the HBLANK code, and a ROM source for the parameters in the VBLANK code.
And yet the codes I put into the ROM were correct, as I went changed the dmacopy_colors
function to just memcpy
the DMA parameters from ROM to RAM, and pointed A0 to RAM as the source of the parameters once again, and everything worked the same. I'm only changing one line of code at this point, the source address of my parameters (both of which hold the same data, one precalculated and the other computed at runtime.) I see no reason why one works and the other does not. I have tested this on the Fusion emulator as well and the same thing happens.
TL;DR: I have two sources of identical inputs, one in ROM the other in RAM. One works and the other does not. Why?