+  RHDN Forum Archive
|-+  Romhacking
| |-+  ROM Hacking Discussion
| | |-+  Midframe HDMA
Pages: [1]
Author Topic: Midframe HDMA  (Read 2 times)
smkd
Guest
« on: August 28, 2007, 04:28:48 am »

I'd like to implement midframe HDMA but I'm getting mixed results in emulators.  Snes9x works as I'd like it to, ZSNES and bSNES do not.  Since bSNES doesn't like it, I suppose I'm doing something wrong.  I set $4308 to what I would in $4302, the bank is set as normal.  Leaving $430A untouched doesn't seem to stop it from working in Snes9x (I am guessing you put your first line count in there?).  In ZSNES and bSNES, nothing triggers.  Snes9x behaves the same if I disable the channel.

Is there something more to it?
creaothceann
Guest
« Reply #1 on: August 28, 2007, 10:05:37 am »

Have you read anomie's register doc? It has a section for that...

Couldn't you just use a table that is long enough? Or is the position not fixed?
byuu
Guest
« Reply #2 on: August 28, 2007, 01:17:54 pm »

It's difficult to debug other's software, and you picked one of the most difficult things to attempt on the SNES, so instead I'll explain how mid-frame HDMA works, and hope that will help point you in the right direction.

First, at the start of every frame, the following is called no matter what:

Code:
void sCPU::hdma_init_reset() {
  for(int i = 0; i < 8; i++) {
    channel[ i ].hdma_completed   = false;
    channel[ i ].hdma_do_transfer = false;
  }
}

Next, if and only if $420c is not clear, the below is called:

Code:
void sCPU::hdma_init() {
  for(int i = 0; i < 8; i++) {
    if(!channel[ i ].hdma_enabled)continue;
    channel[ i ].dma_enabled = false; //HDMA init during DMA will stop DMA mid-transfer

    channel[ i ].hdma_addr = channel[ i ].srcaddr;
    hdma_update(i);
  }
}

void sCPU::hdma_update(uint8 i) {
  channel[ i ].hdma_line_counter = r_mem->read(hdma_addr(i));
  dma_add_clocks( 8 );

  channel[ i ].hdma_completed   = (channel[ i ].hdma_line_counter == 0);
  channel[ i ].hdma_do_transfer = !channel[ i ].hdma_completed;

  if(channel[ i ].hdma_indirect) {
    channel[ i ].hdma_iaddr = r_mem->read(hdma_addr(i)) << 8;
    dma_add_clocks( 8 );

    if(!channel[ i ].hdma_completed || hdma_active_after(i)) {
      channel[ i ].hdma_iaddr >>= 8;
      channel[ i ].hdma_iaddr  |= r_mem->read(hdma_addr(i)) << 8;
      dma_add_clocks( 8 );
    }
  }
}

Next, on every scanline, the below is called, if any [ i ]active[/i], non-terminated channels remain.

Code:
void sCPU::hdma_run() {
static uint8 hdma_xferlen[8] = { 1, 2, 2, 4, 4, 4, 2, 4 };
  for(int i = 0; i < 8; i++) {
    if(hdma_active(i) == false)continue;
    channel[ i ].dma_enabled = false; //HDMA run during DMA will stop DMA mid-transfer
    dma_add_clocks( 8 );

    if(channel[ i ].hdma_do_transfer) {
    int xferlen = hdma_xferlen[channel[ i ].xfermode];
      for(int index = 0; index < xferlen; index++) {
        if(bool(config::cpu.hdma_enable) == true) {
          dma_transfer(channel[ i ].direction, dma_bbus(i, index),
            !channel[ i ].hdma_indirect ? hdma_addr(i) : hdma_iaddr(i));
        } else {
          dma_add_clocks( 8 );
          cycle_edge();
        }
      }
    }

    channel[ i ].hdma_line_counter--;
    channel[ i ].hdma_do_transfer = bool(channel[ i ].hdma_line_counter & 0x80);
    if((channel[ i ].hdma_line_counter & 0x7f) == 0) {
      hdma_update(i);
    }
  }
}

Notice hdma_do_transfer? This is likely where your problem lies. If $420c == 0 at the start of the frame, do_transfer will be set to false. But fortunately (for you at least), hdma_completed is also set to false, so you can still enable HDMA with trickery. Notice in hdma_run(), which runs every line, that it decrements hdma_line_counter. Then, it sets the important flag, do_transfer.

You need this to get set, the test is do_transfer = bool(line_counter & 0x80), so you'll have to set your starting position to $81 in $43xa, [ i ]one line in advance of where you want mid-frame HDMA to start[/i]. This will turn on your transfer (anything < $80 will not), as well as fulfill the next condition, (line_counter & 0x7f) == 0. Remember, the decrement happens first, before those two tests, hence $81 and not $80. We use $81 to minimize the number of lines you have to setup HDMA in advance to just one. The hdma_update() that is called will load a new line counter value (directly or indirectly, depending on how you setup HDMA), so obviously your transfer won't be stuck in continuous mode or anything. It's just a one line thing to trick HDMA to start.

Lastly, you basically have to simulate hdma_init above, since that won't be called when $420c.dN is 0 at the start of the frame. But you probably already know that part.
Nightcrawler
Guest
« Reply #3 on: August 28, 2007, 02:42:37 pm »

I think the BSNES source code deserves a spot in the Documents section. Wink
DaMarsMan
Guest
« Reply #4 on: August 28, 2007, 03:49:55 pm »

I thought I knew quite a bit of how HDMA works.  Lips sealed

*DaMarsMan crawls into a hole and starts to cry*
byuu
Guest
« Reply #5 on: August 29, 2007, 02:40:47 am »

Quote
I think the BSNES source code deserves a spot in the Documents section. Wink

That was kind of my goal, to write the code to be self-documenting. Unlike, say:

Code:
.next_2
lea eax,_x
add eax,ebx
mov bx,word ptr[eax]
xor bx,cx
mov word ptr[eax],bx

... unfortunately, it seems nobody here checks my code first before requesting help, so clearly I haven't done a very good job of that, huh? :/

Or am I being unreasonable? Obviously, we aren't all programmers, but I was kind of hoping an assembly programmer could read C code. Meh, not upset or anything. I don't mind helping by discussing issues either.

Hmm, or maybe an online SVN or something would help.
Nightcrawler
Guest
« Reply #6 on: August 29, 2007, 07:50:20 am »

I don't know. I just took another look at the BSNES source code. I don't think it's very easy to find what you're looking for quickly.

Let's take this case as a specific example. I didn't even initially think to look in sCPU for information on hdma. The first association in my mind was PPU or screen related functionality. Then while I was browsing around some of the bPPU files, it wasn't entirely obvious to me until I looked at a few files that general PPU register information could be found in 'bppu_mmio.cpp'.  I'd say you definitely need to spend 5 or 10 minutes of looking around to start to know where you should be looking to find the information you may be looking for. There are many files and it's not always clear what is in them until you look at them.

It's not plainly obvious where to look immediately. I'm sure looking for something more obscure may be even more difficult to locate the information.

On another note, you have very nice comments in certain files, but plenty of other files have very little if any. Maybe you thought they didn't need any, but I've always preferred too many comments as opposed to not enough.

Walk through the process of how you would have expected this guy or anyone to use the BSNES source to find the answer. I'm interested to know your thought process.


Those are my observations after looking through there for a few minutes. I can understand how somebody may not have found the answer they were looking for. Of course, you also have to understand many people probably don't yet think of BSNES source as a document to go to. I'm very aware of it and it still doesn't pop in my head as something to use for reference very often.
« Last Edit: August 29, 2007, 11:16:22 am by Nightcrawler »
creaothceann
Guest
« Reply #7 on: August 29, 2007, 08:44:37 am »

The problem with code is that it describes in detail how something is done, but not necessarily what. If you already know that, then it's great.
Disch
Guest
« Reply #8 on: August 29, 2007, 03:28:23 pm »

Let's not call apples 'oranges', here.  Source is not documentation.  No matter how clear a program's source is, someone would still have to decipher what it's doing.  Compare that to a document which describes the details of a behavior, and possibly even includes examples and snippits of other information that might provide a more clear picture of what's going on.

I'm not saying source isn't a good tool for learning this kind of stuff.  I'm just saying it can't be a replacement for (or fill the shoes of) actual documentation.  The two serve two entirely different functions.

Not to mention understanding something from an emu standpoint is much different from a hacker/homebrewer standpoint.  Understanding how to emulate the behavior doesn't necessarily tell you how to utilize that behavior from the ROM's end.  This very topic is a perfect example of that.  Someone can look at the code byuu posted and understand the step by step... but they'd still have to fumble around with that information to figure out how to get HDMA to work mid-frame.

A good document, on the other hand, could easily list the steps required to make HDMA work in this fashion.  In fact...  Anomie's doc does exactly that:

Quote
HDMA has 4 flags and 5 variables. Again, those marked '*' are required
before starting HDMA. In addition, those marked '+' are required if HDMA is
to be started mid-frame.
* Addressing Mode (bit 6 of $43x0): If clear, Direct, else Indirect.
* Transfer Mode (bits 0-2 of $43x0): See below...
* Port ($43x1): As for DMA.
* AAddress ($43x2-4): Pointer to the HDMA Table. Not really 'required' for
  starting mid-frame, but unless you're going to stop it before the next
  init...
- Indirect Address ($43x5-6): Used with Indirect Bank. See below...
* Indirect Bank ($43x7): Used with Indirect Address. See below...
+ Address ($43x8-9): See below...
+ Repeat (bit 7 of $43xA): Whether to write every scanline or not
+ Line Counter (bits 0-6 of $43xA): See below...
- DoTransfer: Used internally.

This little snippit tells exactly which regs someone would need to write to in order to make HDMA work.  And exactly which ones are needed to make it work when starting mid-frame.  Compare that to the much larger and much more difficult to understand code snippit (or rather, multiple code snippits) byuu posted.


Note again I'm not saying source isn't useful.  It most certainly is.  I've learned a lot of great stuff by examining others' source that I couldn't have gotten from documents.  But I never liked the "look at the source" response to behavioral questions.


EDIT - I think creaothceann was trying to say more or less the same thing I was, actually  ^^;
Neil
Guest
« Reply #9 on: January 01, 2009, 09:49:58 pm »

-necrobump to save a thread from the great board prune of 2009-
Pages: [1]  


Powered by SMF 1.1.4 | SMF © 2006-2007, Simple Machines LLC