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?