Questions on enabling HDMA mid-frame

Strictly for discussing ZSNES development and for submitting code. You can also join us on IRC at irc.libera.chat in #zsnes.
Please, no requests here.

Moderator: ZSNES Mods

Post Reply
byuu

Questions on enabling HDMA mid-frame

Post by byuu »

I don't seem to quite understand what happens when you do this...

Test program:
http://byuu.cinnamonpirate.com/temp/hdma_midframe.zip

Through emulation, I get this:
Image

But on the SNES, I get this:
Image

Main code:

Code: Select all

.loop
- bit $4212 : bpl -
  stz $420c
- bit $4212 : bmi -

  ldx #$0020
.line_loop
- bit $4212 : bvc -
- bit $4212 : bvs -
  dex : bne .line_loop

  lda $4302 : sta $4308
  lda $4303 : sta $4309
  stz $430a
  lda #$01 : sta $420c

  bra .loop
My understanding is that to enable HDMA mid-frame, you need to set A2AxW and NTRLx. If NTRLx is set to zero, then it just acts as though the register just counted down to zero, and loads the next byte from A2AxW into NTRLx at the next HDMA start and runs with that. But that doesn't seem to be what's happening on-screen here.

I can get what the SNES gets by decrementing A2AxW by one before enabling the HDMA channel.
So it looks like NTRLx isn't loaded, and the HDMA 'resumes' at $4304:($4308). So then I try incrementing $4308 by one, and setting NTRLx to $07, but then I get a black screen on hardware. What the hell? :/
Overload
Hazed
Posts: 70
Joined: Sat Sep 18, 2004 12:47 am
Location: Australia
Contact:

Post by Overload »

Why are you using hdma transfer mode $07? Is that a valid hdma transfer mode? Is it the same as $03?
byuu

Post by byuu »

It's the same as 3. No idea why I chose 7 instead, but it writes to 0,0,1,1. Basically so that I only need one channel to do a gradient fade, it writes 0 to $2121 twice, then writes the color data twice to $2122.
d4s
Rookie
Posts: 28
Joined: Wed Sep 08, 2004 12:05 pm

Post by d4s »

sorry to go slightly offtopic here, but here is something that bugged me for quite some time now:
is it possible to disable individual active hdma channels mid-frame?
byuu

Post by byuu »

I don't know why you'd want to (just use a line count of $00 to end the HDMA for that channel), but yes. I'm 99% certain that clearing the bit in $420c should be sufficient for stopping the HDMA. You'd have to manually enable the bit again for HDMA to take effect on the next frame, though.
anomie
Lurker
Posts: 151
Joined: Tue Dec 07, 2004 1:40 am

Re: Questions on enabling HDMA mid-frame

Post by anomie »

Here's what I have noted as the process the SNES follows for each line's HDMA transfer. Note that the 'DoTransfer' flag is always initially false when starting a transfer mid-frame.

Code: Select all

 1. If DoTransfer is false, skip to step 3.
 2. For the number of bytes (1, 2, or 4) required for this Transfer Mode...
    a. Read a byte from Address or Indirect Address, and increment.
    b. Write the byte to Port, Port+1, Port+2, or Port+3, depending on the
       Transfer Mode and which byte we're on.
 3. Set DoTransfer to the value of Repeat [NTLRx bit 7]..
 4. Decrement $43x0 [NTLRx]. If Line Counter [NTLRx&0x7F] is
    zero [after the decrement]...
    a. Read the next byte from Address into $43xA (thus, into both Line
       Counter and Repeat).
    b. If Addressing Mode is Indirect, read two bytes from Address into
       Indirect Address (and increment Address by two bytes). One oddity: if
       $43xA is 0, load from Address-1 instead of Address and only inc Address
       by 1.
    c. If $43xA is zero, stop this HDMA channel for this frame. The bit in
       $420c is not cleared, though, so it may be automatically restarted
       next frame.
    d. Set DoTransfer to true.
 5. Continue with Step 1 next scanline.
BTW, my version of snes9x with HDMA code matching the above gets your test ROM right ;)

I've never tested trying to stop a transfer mid-frame. Certainly, pointing Address (A2AxL and A2AxH, IIRC) at zero bytes (5 at most, if DoTransfer and a 4-byte Direct mode) and writing $01 to NTLRx should do it. Don't know about writing $420c, that may just take effect for the next frame.
byuu

Post by byuu »

Cool, thanks :D
One oddity: if
$43xA is 0, load from Address-1 instead of Address and only inc Address
by 1.
I recently added in the HDMA+HDMA init timing (similar to DMA timing, seems to be right). I've only tested having $00 as the first byte of the table, ex:

$00,$12,$34

Of course, NTLRx gets set to $00, and then DASxW becomes $1200, and A2AxW gets incremented by two instead of three. The interesting part is that only two reads have occurred, so it isn't actually reading the $00 twice. At least, that's what I have to do to get OPHCT latches right.
I'm guessing when $00 is read in, it skips over the logic that reads in the next byte from the table, and not just the increment to A2AxW.
That, or it clears DASxW and each load moves the high byte to the low byte, and then loads the next read into the high byte each time. Same effect either way.
Again, only tested this with HDMA init having the first NTLRx as zero, and not during a standard HDMA event. I can't imagine it being any different though.
<Note: may have the register names wrong, but you see what I'm saying I'm sure...>

HDMA init happens at different spots on CPUr1 and CPUr2, by the way. Not at my main PC, so I'll post the specifics tomorrow or something. Seems to be based on the 8-cycle DMA counter. HDMA itself, at least on CPUr1, does not. Happens at HC=1106. Didn't test HDMA on the CPUr2 yet.
byuu

Post by byuu »

Alright, got it. SFA2 works fine now. As usual, I had things three times more complex than need be.

One thing to note, on 4d it sounds like you're saying that DoTransfer should always be set to true when NTLRx is loaded... and above you mention that dotransfer is always false when starting mid-frame. That would suggest that writing to $420c would clear DoTransfer. That, or DoTransfer is cleared at V>=vblank or maybe at HDMA init.

So what I did was disable HDMA, set the back color to blue and wait until the start of vblank, then enable channel 1, which was set to change the back color to red. Then I waited until V=0, and then disabled HDMA, and wrote $00 to NTLRx, then enabled HDMA again. Then waited until V=1 and disabled HDMA for good. This resulted in the back color being set to red, so writing to $420c has no effect on DoTransfer. I suspect that when NTLRx is set to $00 (only during hdma init or hdma run, not from manually writing to it), it clears do transfer.

Code: Select all

  stz $420c
  stz $2121
  stz $2122
  lda #$7c : sta $2122
  lda #$0f : sta $2100

- bit $4212 : bpl -
  lda #$01 : sta $420c
- bit $4212 : bmi -
  nop #8
  stz $420c
  stz $430a
  lda #$01
  sta $420c ;* disabling this line will prevent HDMA transfer from occurring

- bit $4212 : bvc -
- bit $4212 : bvs -

  stz $420c
  stp
I also tested disabling HDMA right after HDMA init and before the first HDMA transfer, and that did in fact stop the HDMA transfer on line 0 from happening, so yeah, just clearing $420c will stop HDMA immediately.

The only HDMA bugs left that I'm aware of are Charlie's stage in SFA2 (the plane thing skips around oddly), and of course, Energy Breaker. That game is just... mad. It's better than it was before though, I guess.
anomie
Lurker
Posts: 151
Joined: Tue Dec 07, 2004 1:40 am

Post by anomie »

byuusan wrote:One thing to note, on 4d it sounds like you're saying that DoTransfer should always be set to true when NTLRx is loaded... and above you mention that dotransfer is always false when starting mid-frame. That would suggest that writing to $420c would clear DoTransfer. That, or DoTransfer is cleared at V>=vblank or maybe at HDMA init.
HDMA Init sets DoTransfer. Step 3 will set/clear DoTransfer, and 4d may force it to be set. ... I guess there's an unstated assumption that DoTransfer is clear when the channel is not active, so enabling HDMA mid-frame begins with it clear.


So what I did was disable HDMA, set the back color to blue and wait until the start of vblank, then enable channel 1, which was set to change the back color to red. Then I waited until V=0, and then disabled HDMA, and wrote $00 to NTLRx, then enabled HDMA again. Then waited until V=1 and disabled HDMA for good. This resulted in the back color being set to red, so writing to $420c has no effect on DoTransfer. I suspect that when NTLRx is set to $00 (only during hdma init or hdma run, not from manually writing to it), it clears DoTransfer. I don't think writing NTRLx directly affects it either way.
I also tested disabling HDMA right after HDMA init and before the first HDMA transfer, and that did in fact stop the HDMA transfer on line 0 from happening, so yeah, just clearing $420c will stop HDMA immediately.
Good to know.
byuu

Post by byuu »

HDMA Init sets DoTransfer. Step 3 will set/clear DoTransfer, and 4d may force it to be set. ... I guess there's an unstated assumption that DoTransfer is clear when the channel is not active, so enabling HDMA mid-frame begins with it clear.
Right, HDMA init would since it loads NTLRx.
BTW, it is NTLRx and not NTRLx right? Your docs have NTRLx.
I don't even know what half the acronyms mean >_<

So if dotransfer is clear when the channel is not active, then when it reads the $00 for NTLRx it goes clear along with the active flag. Ok. It's really the same thing either way. The only cause for concern was if writes to $420c cleared dotransfer, which they don't.
Overload
Hazed
Posts: 70
Joined: Sat Sep 18, 2004 12:47 am
Location: Australia
Contact:

Post by Overload »

byuusan wrote:BTW, it is NTLRx and not NTRLx right? Your docs have NTRLx.
I don't even know what half the acronyms mean >_<
The DMA Registers don't have official names or according to the documentation I have they don't. Really it should be
The Number of Lines to be TRansfered by H-DMA.
DMV27
New Member
Posts: 9
Joined: Thu Jan 27, 2005 5:03 pm

Post by DMV27 »

byuusan wrote:The only cause for concern was if writes to $420c cleared dotransfer, which they don't.
The Energy Breaker map requires writes to $420C to clear the DoTransfer flag for a channel when that channel is disabled. My guess is that HDMA always runs on scanline zero, even if the DoTransfer flag is clear. You should try to run your blue/red test again starting at V=1 or 2 instead of V=0.

Also, the Contra 3 title screen requires that the DoTransfer flag be set to the repeat flag after NTLRx is decremented. I think this is the correct method:

Code: Select all

1. If DoTransfer is true or the scanline number is zero...
   a. For the number of bytes (1, 2, or 4) required for this Transfer Mode...
      1. Read a byte from Address or Indirect Address, and increment.
      2. Write the byte to Port, Port+1, Port+2, or Port+3, depending on the
         Transfer Mode and which byte we're on.
2. Decrement $43xA [NTLRx]. (Note: $00 - 1 is $FF, not $7F)
   a. If Line Counter [NTLRx & 0x7F] is zero [after the decrement]...
      1. Read the next byte from Address into $43xA (thus, into both Line
         Counter and Repeat).
      2. If Addressing Mode is Indirect, read two bytes from Address into
         Indirect Address (and increment Address by two bytes).
         One oddity: if $43xA is 0, load from Address-1 instead of Address
         and only inc Address by 1.
      3. If $43xA is zero, stop this HDMA channel for this frame, and set
         DoTransfer to false. The bit in $420c is not cleared, though, so
         it may be automatically restarted next frame.
      4. Else if $43xA is not zero, set DoTransfer to true.
   b. Else If Line Counter is not zero...
      1. Set DoTransfer to the value of Repeat [NTLRx bit 7].
         (0 = false, 1 = true)
      2. Tick the cpu clock one slow cycle.
3. Continue with Step 1 next scanline.
byuu

Post by byuu »

The Energy Breaker map requires writes to $420C to clear the DoTransfer flag for a channel when that channel is disabled.
Nope.
Image

The "DoTransfer" flag is cleared for all channels either on the last HDMA run for the frame, or during each HDMA init, regardless if any channels are enabled or not. I'm not sure which, but the effect is the same either way. I'll make an overscan HDMA toggle test later I guess.

I have a test ROM where I disable HDMA and cross over to V=0, pass HDMA init, and then enable HDMA. Wait until V=1, then disable HDMA, write $00 to NTLRx, then enable it again. The HDMA transfer occurs at V=1,HC=1106. So writes to HDMA regs ($420c / $43xa included) do not clear DoTransfer.

Whereas, if you run a frame with HDMA enabled, and it ends with dotransfer set, and then you disable HDMA during vblank, then wait until V=0 and enable it, no HDMA transfer occurs at V=0,HC=1106.
Also, the Contra 3 title screen requires that the DoTransfer flag be set to the repeat flag after NTLRx is decremented.
That was an error in anomie's document. You have to decrement NTLRx, and then set dotransfer to bit 7 of NTLRx.
Otherwise, if you load $80 for NTLRx, two transfers would occur. One after loading, and one on the next HDMA run.

Oh yeah, with all of this implemented, now FF:MQ is totally screwed in-game :/

Here's what I have so far. wip6
byuu

Post by byuu »

Hm, ok the FF:MQ bug was unrelated. Turns out my code didn't handle DMA/HDMA conflicts very well after adding in the HDMA bus sync timing.
I honestly don't even know what the hell it was doing when that happened.
So I emulated that as best as I can guess hardware does it, and it works fine now.

I also tested one new thing while I was at it. Specifically, I made HDMA trigger with NTLRx set to 0. The transfer occurs based on the DoTransfer flag, and then it decrements and becomes 0xff. So it becomes "repeat 127(128?) times". So HDMA definitely doesn't check NTLRx before doing the transfer and decrementing the register first. As we already knew, but extra verification never hurts. I can't think of anything else to test, and know of no games with broken HDMA effects, so I think we're golden.
Post Reply