8

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:

enter image description here

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.

enter image description here

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.

enter image description here

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?

14
  • 3
    You also tested this on real hardware?
    – Tommy
    Aug 7, 2022 at 11:25
  • 1
    The folks on Spritesmind would know more about this
    – knol
    Aug 7, 2022 at 12:06
  • 1
    I haven't tested on real hardware, no. Aug 7, 2022 at 18:40
  • 1
    @puppydrum64 if you can put suitable ROM images somewhere, I can test it for you. I have a Nomad and an Everdrive.
    – Tommy
    Aug 7, 2022 at 18:51
  • 2
    Just a quick thought. HBLANK isn't very long, I'd be surprised if there are more then 80 or so CPU cycles usable, so if ROM needs wait states it may screw up the whole timing, while the RAM version barely makes it?
    – Raffzahn
    Aug 7, 2022 at 20:24

1 Answer 1

10

I can't believe I only see this topic so long after it was post !

For reference: I made the 68k optimizations of the video engine of a recent and quite famous game.

The Genesis Software Manual scanned by Ozisoft (and manually OCRified into the Genesis Technical Overview) document states:

In the case of ROM to VRAM transfers, a hardware feature causes occasional failure of DMA unless the following two conditions are observed:

  • The destination address write (to address $C00004) must be a word write.
  • The final write must use the work RAM. There are two ways to accomplish this: (1) by copying the DMA program into RAM or (2) by doing a final "move.w ram_address,$C00004"

(emphasis mine)

In his Sega Genesis VDP Documentation, Charles MacDonald states:

When a transfer is done out of the ROM area ($000000-3FFFFF), the machine will lock up unless the write that triggers the DMA operation is done using RAM. Usually this means

  • putting the command word or the latter half of the command word in RAM and moving that into the control port,
  • putting the command word on the stack and moving that into the control port,
  • or having the instruction that moves the command word into the control port execute out of RAM.

(layout mine)

So yes, “using the (An)+ to Memory addressing mode when address register is pointed to ROM causes Sega Genesis DMA to fail”

It is a bug Sega has known since the very beginning of the Genesis.

5
  • As a point of clarification, would it be fair to describe the requirement as "DMA from ROM must not be triggered between a ROM access and the next RAM access"?
    – supercat
    Jun 8 at 17:27
  • 1
    @supercat I'm not sure I would go that far. I'd rather stick to the two quotes above, which both seem clearer to me. Plus, I'm more into using them than clarifying them. But if I had to describe this, I would start from the hardware. I think I remember about the DMAC asking for the bus, the arbiter, and the 68k being let down between two steps. But all that is pretty shady to me, and this would need its very own question ^^
    – Olivier
    Jun 8 at 19:11
  • 1
    The statement "the final write must use the work RAM" doesn't seem consistent with what is being described, since the final write itself must be a word write to address $C00004, which isn't RAM. Saying "the instruction that performs the final write must use work RAM" might be best.
    – supercat
    Jun 8 at 19:37
  • 1
    FWIW I don't think anyone here would begrudge you editing this answer to include the name (or even a link to) the game you've mentioned!
    – Matt Lacey
    Jun 9 at 12:10
  • 1
    Wow, time sure flies. I forgot I even asked about this! I kind of gave up on Genesis (and programming altogether for the time being) but it's good to know that this is a known hardware bug and not something I did wrong. Jun 26 at 18:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .