gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

ASM > Syncing IPC/FIFO

#166047 - Tyler24 - Sun Jan 18, 2009 9:09 pm

I'm trying to avoid using libnds because I feel it's a little bloated for my tastes. I'm coding in assembly instead of C, and in order to write to the screen (at least in MODE_FB0) I realized that the processors have to be synced via IPC and FIFO has to be enabled and ready to go.

I've been trying to look at fifocommon.h, specifically fifoInit()... but nothing is commented. And I have no idea how to sync make the processors sync via the hardware IPC. I have the IPC sync register address and everything from GBAtek, but there's no documentation about how to actually go about syncing processors anywhere.

The sync code I have now works (in C), taken straight from the libnds source code. I have no idea what it's doing, unfortunately, and would like to understand:

Code:
      REG_IPC_SYNC = 0;
      while(counted < 3)
      {
         their_value = REG_IPC_SYNC&0x000F;
         if( ((my_value + 1)&0x000F ) == their_value ) counted++;
         my_value=(their_value+1)&0x000F;
         REG_IPC_SYNC = my_value<<8;
      }
      REG_IPC_SYNC = 14 <<8;


Thanks in advance!

#166048 - Maxxie - Sun Jan 18, 2009 9:40 pm

It's doing a 4-time challenge and response (with a simple increase by one response) to make sure the other side is in this very same game.

This is done, because you can not tell simply by the registers content at which state it is because it's value is unknown before initialisation and you don't know whether or not the initialization on the other side has been done yet.

The challenge and response game however has to be played on both cpus or it won't pass (Or in very very unlikely circumstances)
However, i am missing a reset-on-failed challenge.
_________________
Trying to bring more detail into understanding the wireless hardware

#166051 - Tyler24 - Sun Jan 18, 2009 10:08 pm

Maxxie wrote:
It's doing a 4-time challenge and response (with a simple increase by one response) to make sure the other side is in this very same game.

This is done, because you can not tell simply by the registers content at which state it is because it's value is unknown before initialisation and you don't know whether or not the initialization on the other side has been done yet.

The challenge and response game however has to be played on both cpus or it won't pass (Or in very very unlikely circumstances)
However, i am missing a reset-on-failed challenge.


Thanks for the reply. I can get some vague idea of a challenge and response going on. If this is your code, it initially had error checking and whatnot, but I deleted all of it to figure out which parts I really needed for my example.

The ARM9 code simply selects VRAM_A, MODEFB_0 and draws a square. No IPC/FIFO initialization code here. For some reason, however, the IPC/FIFO has to be initialized on the ARM7 side, or all I get is black screens, even on real hardware. I'm scratching my head as to why it only needs to be done on the ARM7 side of things, but it does.

Do you have any documentation on this, or is it all based off lots of trial-and-error? This is the C code that works as of now (it's all from the ndslib source though):

ARM7:
Code:

---------------------------------------------------------------------------------*/
#include <stdint.h>

#define IPC_FIFO_ENABLE (1 << 15)
#define IPC_FIFO_SEND_CLEAR (1 << 3)
#define REG_IPC_SYNC *(volatile uint16_t*) 0x04000180
#define REG_IPC_FIFO_CR *(volatile uint16_t*) 0x4000184
#define IPC_SYNC_IRQ_ENABLE (1 << 14)

//---------------------------------------------------------------------------------
int main() {
//---------------------------------------------------------------------------------

   // initialize fifo and sync to other cpu
   REG_IPC_FIFO_CR = IPC_FIFO_ENABLE | IPC_FIFO_SEND_CLEAR;
   {
      int counted = 0;
      int my_value = 0, their_value;
      REG_IPC_SYNC = 0;
      while(counted < 3)
      {
         // sync
         their_value = REG_IPC_SYNC&0x000F;
         if( ((my_value + 1)&0x000F ) == their_value ) counted++;
         my_value=(their_value+1)&0x000F;
         REG_IPC_SYNC = my_value<<8;
      }
      REG_IPC_SYNC = 14 <<8;
   }

   // Stick it in an infinite loop
   while (1);
}


ARM9:
Code:

#include <stdint.h>

typedef int16_t int16;
typedef uint16_t uint16;
typedef volatile uint32_t vu32;
#define REG_DISPCNT  *(vu32*) 0x04000000
#define VRAM_A_CR  *(vu32*) 0x04000240
#define VRAM_ENABLE (1 << 7)
#define VRAM_A_LCD  0
#define VRAM_A  ((uint16_t*) 0x6800000)
#define RGB15(r,g,b) ((r) | ((g)<<5) | ((b)<<10) )

static int shape_width = 50;
static int shape_height = 50;

void draw_shape(int x, int y, uint16* buffer, uint16 color)
{
   int i, j;

  buffer += y * 256 + x;
  for(i = 0; i < shape_height; ++i) {
    uint16* line = buffer + (256 * i);
    for(j = 0; j < shape_width; ++j) {
      *line++ = color;
    }
  }
}

int main(void)
{
  //videoSetMode(MODE_FB0);
  REG_DISPCNT = 0x00020000;
  //vramSetBankA(VRAM_A_LCD);
  VRAM_A_CR = VRAM_ENABLE | VRAM_A_LCD;

  draw_shape(5, 5, VRAM_A, RGB15(31, 0, 0));


  while(1);
  return 0;
}

#166052 - Maxxie - Sun Jan 18, 2009 10:24 pm

Tyler24 wrote:
If this is your code, it initially had error checking and whatnot, but I deleted all of it to figure out which parts I really needed for my example.


No it is not mine.
If you got it, reinsert the error checking to prevent lock-ups.

Quote:

The ARM9 code simply selects VRAM_A, MODEFB_0 and draws a square. No IPC/FIFO initialization code here.


There is but not in main:
The first thing to be called before executing main() is the crt0. The file containing the code (arm assembler) is calling initSystem (see http://devkitpro.svn.sourceforge.net/viewvc/devkitpro/trunk/libnds/source/arm9/initSystem.c?revision=2967&view=markup) Which calls the fifoInit (see http://devkitpro.svn.sourceforge.net/viewvc/devkitpro/trunk/libnds/source/common/fifosystem.c?revision=2801&view=markup) . That's where it's done.

The arm9 will never reach main() if the crt0 does not pass the initSystem call.
_________________
Trying to bring more detail into understanding the wireless hardware

#166053 - Tyler24 - Sun Jan 18, 2009 10:33 pm

Ahh, didn't even think to peek at the crt0 code. I see it now, thanks.

Code:
   push   {r0}
   ldr   r3, =initSystem
   blx   r3         @ system initialisation


This is going to take a lot more work than I had hoped for to get some communication going. I'm still confused as to how to link the processors together via the IPC... as in, what needs to be "set" on both ends. I can see the challenge and response going on that you mentioned, but what is set/done as a result of it? What is preventing me from drawing on the screen if that challenge/response code doesn't execute in the ARM7 C code I provided? EDIT: Ahh, is the timeout on the other side of things preventing me from getting anywhere? I made my own template folder, which uses its own makefile, and links itself from all of my own code and such. When I run it in iDeaS, I can open the debugger and the ARM9/7 code is exactly where I specified, and when I run it, all of the resulting changes are expressed in memory. I tried specifying VRAM_A/MODE_FB0 and copied some memory to VRAMA. The debugger shows that there indeed was memory written to VRAMA, but nothing shows up on the screen. Now I'm beginning to wonder if it's for some other reason...?

This is all the information on the IPC Sync Register:
Code:
4000180h - NDS9/NDS7 - IPCSYNC - IPC Synchronize Register (R/W)
  Bit   Dir  Expl.
  0-3   R    Data input from IPCSYNC Bit8-11 of remote CPU (00h..0Fh)
  4-7   -    Not used
  8-11  R/W  Data output to IPCSYNC Bit0-3 of remote CPU   (00h..0Fh)
  12    -    Not used
  13    W    Send IRQ to remote CPU      (0=None, 1=Send IRQ)
  14    R/W  Enable IRQ from remote CPU  (0=Disable, 1=Enable)
  15-31 -    Not used

#166054 - Tyler24 - Sun Jan 18, 2009 11:31 pm

Sorry for the double post, but I found my problem! Solution for those who care:

I realized that I wasn't getting anything with the C code because as Maxxie helpfully pointed out, the ARM9 code was calling fifoInit as well as the ARM7 code, even though I hadn't explicitly called it (it was done in initSystem() in ds_arm9_crt0.s). When I got rid of the fifoInit() code from the ARM7 (or fiddled with it so much that I ended up destroying it) I created the lockup. Blank screens. One of the processors was stuck in the timeout loop.

Stupid me, on the other hard, was going along too fast and missed a 0 in my own assembler code. My ROM was getting assembled properly, but I wasn't writing to the display register, and thus nothing ever happened. Haha. Stupid me.

#166082 - headspin - Mon Jan 26, 2009 9:24 am

I am writing a project for the DS in pure asm (without libnds) and I am using IPC (0x027FF000) for music and have not found it necessary to use a method to sync the two CPU's.

I read from this website that the IPC memory is the first mirror of main memory and is uncached. It also states that "Both the ARM7 and the ARM9 can access this [the main] memory at any time. Any bus conflicts are delegated to the processor which has priority (the ARM7 by default but changeable via a control register) causing the other processor to wait until the first has finished its operation."

So I just simply write to the IPC area from the ARM9 and ARM7 reads it. I have not had any problems and seems to work okay on hardware. If it's uncached I also assume I don't need to flush.
_________________
Warhawk DS | Manic Miner: The Lost Levels | The Detective Game

#166088 - DekuTree64 - Mon Jan 26, 2009 11:10 am

IPC FIFO is different from shared memory. Here is a simpler FIFO transfer system I wrote a long time ago, that might help you understand it a bit better (I'm also puzzled by the massive amount of code in the libnds one).

Mine has some annoying shared memory counter things, but I think they ended up being kind of pointless aside from giving the ability to wait for the other CPU to process a command, so you could remove them (and the IPCFifoCheckMessageDone and IPCFifoWaitMessage functions).

It doesn't look like I messed with REG_IPC_SYNC at all. Actually I'm not entirely sure what that register was invented for. I don't think it adds any abilities that you can't do using the FIFO.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#166093 - Maxxie - Mon Jan 26, 2009 2:38 pm

DekuTree64 wrote:

It doesn't look like I messed with REG_IPC_SYNC at all. Actually I'm not entirely sure what that register was invented for. I don't think it adds any abilities that you can't do using the FIFO.


Its much more ease. You can do a lot of things wrong when syncing via fifo including i.e. forgetting that the other side might clear your sync requests aways when initializing itself.

Surely you can do everything a second way, but that's not really the point. We could remove the mul opcodes, as we can add allready and rebuild the mul that way ;)
_________________
Trying to bring more detail into understanding the wireless hardware