Home New Tutorials Programs
1-R4DS Setup 2-Software Space Invaders
Old Tutorials DSEmu
1-Setup 4-Sound 7-FIFO
2-Framebuffer 5-SRAM 8-Interrupts
3-Keys 6-Filesystems 9-Microphone
10-Extended Rotation Background

Homebrew Nintendo DS Development Part 7

FIFO

This tutorial was tested and works with libnds dated 2005-10-18. Unfortunately it no longer works with libnds dated 2005-12-12. I'm looking into it.

In tutorial six we came up with a way of having the ARM9 send messages to the ARM7 to notify it to play sounds. While this worked well it has at least two main problems.

The first is that it requires storing data in shared memory with the ARM7 constantly polling that memory to find out if a message has arrived for it. The second is there was no way for the ARM7 to communicate results back to the ARM9. While there are ways of working around some of these problems, a better way exists.

The Nintendo DS has a built in FIFO queue. FIFO stands for 'First In, First Out'. This type of data structure allows a 'sender' to put data on the queue one by one. A 'receiver' can retrieve items from the queue, starting with the first item the sender placed in it (hence the First In First Out name).

Although the FIFO can be polled to see if items exist, it also allows interrupts to be performed when things are placed on or removed from the queue. This is much more efficient than polling. So we could have the ARM9 putting sound commands into the FIFO and the ARM7 would immediately get notified that the queue has an item in it and start playing the sound.

The only documentation I've come across on the FIFO is from reading the DSLinux sources and DSTek. The rest of my knowledge is from experimenting with the registers and trying different things out. We'll start with a simple program that displays the registers, allows placing things in and removing things from the queue, and shows the results on the registers.

FIFO Registers

There are three memory registers involved in the FIFO:

DefineAddressSizeDescription
REG_IPCFIFOCNT0x0400018416 bitsUsed for getting the status of the FIFO, reseting it, setting interrupts, etc.
REG_IPCFIFOSEND0x0400018832 bitsWrite only register for sending data to the FIFO.
REG_IPCFIFORECV0x0410000032 bitsRead only register for retrieving data from the FIFO.

REG_IPCFIFOCNT

The control register has 16 bits of information. The ones that I know about are:

DefineBitRead/WriteDescription
IPC_FIFO_SEND_EMPTY0RClear if nothing has been sent, otherwise set.
IPC_FIFO_SEND_FULL1RSet if the send queue is full.
IPC_FIFO_SEND_IRQ2R/WIf set an interrupt will occur when something is put on the queue.
IPC_FIFO_SEND_CLEAR3WEmpties the send queue when set.
IPC_FIFO_RECV_EMPTY8RClear if nothing is in the receive queue, otherwise set.
IPC_FIFO_RECV_FULL9RSet if the receive queue is full.
IPC_FIFO_RECV_IRQ10R/WIf set an interrupt will occur when something is received from the queue.
IPC_FIFO_ERROR14RSet if an error occurs during a send or receive.
IPC_FIFO_ENABLE15R/WEnables the FIFO queue.

REG_IPCFIFOSEND

This is a 32 bit write only register. When a value is written to the register it is placed in the send queue for the processor. This queue can only hold 16 items so the other processor should be receiving these items in a timely manner.

When an item is written to the register bits will change in the control registers on the different CPU's. The value of the control register is different for each CPU. The table below shows the effects on the bits in the control register when the 'sending CPU' writes to the send register:

NameEffect
Sending CPU
IPC_FIFO_SEND_EMPTYIf cleared it will now be set as the send queue is no longer empty.
IPC_FIFO_SEND_FULLSet if the send queue is full as a result of this send.
IPC_FIFO_ERRORSet if the send queue was already full.
Receiving CPU
IPC_FIFO_RECV_EMPTYIf cleared it will now be set as the receive queue is no longer empty.
IPC_FIFO_RECV_FULLSet if the receive queue is full as a result of the send.

From this you can surmise that the sending processors send queue is the receiving processors receive queue.

REG_IPCFIFORECV

This is a 32 bit read only register. When the receive queue is not empty (ie. IPC_FIFO_RECV_EMPTY of the control register is clear) you can read from this register to get the value that the other processor put on it.

When an item is read from the register bits will change in the control registers on the different CPU's. The table below shows the effects on the bits in the control register when the 'receiving CPU' reads from the receive register:

NameEffect
Receiving CPU
IPC_FIFO_RECV_EMPTYThis is cleared if the item received was the last item in the receive queue.
IPC_FIFO_RECV_FULLCleared if the queue was full before the receive was performed. Receiving the last item means the queue is no longer full.
IPC_FIFO_ERRORSet if an attempt was made to receive from an empty receive queue.
Receiving CPU
IPC_FIFO_SEND_EMPTYThis is cleared if there are no more items in the send queue for this processor.
IPC_FIFO_SEND_FULLCleared if the queue was full before the receive was performed. Receiving the last item means the queue is no longer full.

FIFO Usage

From the register explanation it should be a bit clearer how the FIFO works. Each CPU has a queue which it can put data on. This is done by writing any 32-bit value to REG_IPCFIFOSEND. The other CPU can receive this data by reading from REG_IPCFIFORECV. It will get the oldest item that the original CPU has put on the queue.

The bits in the control register, REG_IPCFIFOCNT, can be used to detect if there are items to be read and if the queue is empty or full.

Up to 16 items can be placed in the queue before it is full. This is 16 items in each send queue. So the ARM9 can have 16 items that it has sent to the ARM7 and the ARM7 can have 16 items sent to the ARM9 at the same time. Although the same register numbers are used, each CPU has it's own queue allowing bidirectional communication.

To retrieve data from the queue using polling it's probably best to check to see if the receive queue is empty, and if it is not then process an item from it. Or loop, processing all items. Something like:

  // One item
  if(!(REG_IPCFIFOCNT & IPC_FIFO_RECV_EMPTY)) 
    processItem(REG_IPCFIFORECV);

  // All items
  while(!(REG_IPCFIFOCNT & IPC_FIFO_RECV_EMPTY))
    processItem(REG_IPCFIFORECV);    

The loop approach has the possibility of never ending if the sending processor is also in a loop adding items to the queue. You could also use interrupts to be notified when the receiving queue has data in it. This is a topic for a later tutorial.

Example Application Usage

The example application I used to test this out is called fifo_demo1. It uses simple console output to display the contents of the registers and with button presses allows the ARM7 and ARM9 to send and receive data. The effect on the registers is displayed.

When the users presses 'Up', the FIFO register for a particular CPU is enabled. When 'Right' is pressed, an item of data is written to the send register. I use a simple incrementing number. When 'Down' is pressed an item is read from the receive register and stored in a variable for display during the VBlank interrupt.

The CPU that does the operation is controlled by the left and right shoulder keys. Holding the left shoulder key down while pressing the directional buttons results in the operations occurring on the ARM7. Holding the right shoulder button down uses the ARM9.

Shoulder ButtonDirection ButtonAction
LeftUpEnable ARM7 FIFO
LeftRightWrite on the ARM7's send queue
LeftDownReceive from the ARM7's receive queue
RightUpEnable ARM9 FIFO
RightRightWrite on the ARM9's send queue
RightDownReceive from the ARM9's receive queue

A simple test of operation is:

  1. On initial startup the screen shows:
    ARM9 Fifo Registers:
    REG_IPCFIFOCNT:      101
    REG_IPCFIFORECV:   
    ARM7 Fifo Registers:
    REG_IPCFIFOCNT:      101
    REG_IPCFIFORECV:   
    
    The numbers are in hexadecimal. This shows that the FIFO's start off disabled with the send and receive queue's empty (0x0101 is 0b100000001. Bits 0 and 8 are set for send and receive empty respectively).
  2. Holding the right shoulder button down and pressing the up directional arrow results in the FIFO being enabled on the ARM9 using the following code:
        if((keysHeld() & KEY_R) && (keysDown() & KEY_UP)) {
          REG_IPCFIFOCNT = IPC_FIFO_ENABLE | IPC_FIFO_SEND_CLEAR;
        }
    
    The screen updates to show that bit 15 of the control register for the ARM9 has been set, making it enabled. The ARM7 value does not change:
    ARM9 Fifo Registers:
    REG_IPCFIFOCNT:      8101
    REG_IPCFIFORECV:   
    ARM7 Fifo Registers:
    REG_IPCFIFOCNT:      101
    REG_IPCFIFORECV:   
    
  3. The next step is to hold the right shoulder button down and press the Right directional key. This does a write to the REG_IPCFIFOSEND register from the ARM9. An incrementing integer number is written as follows:
        if((keysHeld() & KEY_R) && (keysDown() & KEY_RIGHT)) {
          static int count = 0;
          REG_IPCFIFOSEND = ++count;
        }
    
    The change in the registers is shown on the screen. The value of the control register on the ARM9 changes to 0x8100. the only change is that bit 0 is now clear, meaning the send queue is no longer empty. The value of the ARM7 control register is 0x0001. Bit 8 is now clear meaning its receive queue is no longer empty. Bit 1 is still set showing that the ARM7's send queue is empty. Remember there are two queues, one for each direction (ARM7->ARM9 and ARM9->ARM7). We just put something on ARM9's send queue so that shows as not empty whereas we haven't touched ARM7's so it is still showing empty.
    ARM9 Fifo Registers:
    REG_IPCFIFOCNT:      8100
    REG_IPCFIFORECV:   
    ARM7 Fifo Registers:
    REG_IPCFIFOCNT:      1
    REG_IPCFIFORECV:   
    
  4. Lets receive the value on the ARM7's side. First we need to enable the FIFO on the ARM7. Hold the left shoulder button down while pressing the up arrow key. This writes to the REG_IPCFIFOCNT register on the ARM7 using similar code to the ARM9 code shown previously. The register change shows that bit 15 on the control register on the ARM7 is now set, showing it is enabled:
    ARM9 Fifo Registers:
    REG_IPCFIFOCNT:      8100
    REG_IPCFIFORECV:   
    ARM7 Fifo Registers:
    REG_IPCFIFOCNT:      8001
    REG_IPCFIFORECV:   
    
  5. Hold the left shoulder button while pressing the down arrow key. This results in code running on the ARM7 to read REG_IPCFIFORECV and store it in a variable that we can read and display on the ARM9:
      arm7_fifo->recv = REG_IPCFIFORECV;
    
    The control register on the ARM9 changes back to 8101. The send queue has changed back to empty since we've received the value. On the ARM7 the value is also 8101. Our receive queue is empty since we've received the only value on it. The REG_IPCFIFORCV field on the screen for the ARM7 seven shows '1' which is the value the ARM9 sent.
    ARM9 Fifo Registers:
    REG_IPCFIFOCNT:      8101
    REG_IPCFIFORECV:   
    ARM7 Fifo Registers:
    REG_IPCFIFOCNT:      8101
    REG_IPCFIFORECV:     1
    
  6. Try holding the right shoulder button down and pressing the right directional key 16 times. This causes a write on the send register from the ARM9 16 times - the maximum number of values the queue can hold. On the 16th time the ARM9's control register will be 0x8102. The IPC_FIFO_SEND_FULL bit is set showing that the send queue is full. The ARM7's value is 0x8201. It shows the IPC_FIFO_RECV_FULL bit is set, meaning its receive queue is full.
    ARM9 Fifo Registers:
    REG_IPCFIFOCNT:      8102
    REG_IPCFIFORECV:   
    ARM7 Fifo Registers:
    REG_IPCFIFOCNT:      8201
    REG_IPCFIFORECV:     1
    
  7. Hold the left shoulder button down and press the down arrow sixteen times to fully empty the queue. Each press of the down arrow should display the numbers sent from the ARM9 in a FIFO manner. So it will be 2 then 3, 4, 5, etc. The first time will immediately change the ARM9 control register to 0x8100 and the ARM7 to 0x8001 as the queue full flags are cleared. On the 16th time the queue empty flags will be set.
  8. Try holding the right shoulder button down and pressing the left directional 16 times to fill the ARM9's queue and do the same holding the left shoulder button down to fill the ARM7's queue. Then alternately use the left and right shoulder buttons while pressing the down arrow. This will receive a value, one at a time, alternating on the ARM7 and ARM9. This shows that each processors queue is independant and can hold 16 values.
  9. When a queue is empty, try receiving a value on it. The IPC_FIFO_ERROR bit will be set on the control register for that processor indicating you tried to receive a value that had not been sent. This bit will also be set if you try to send to an already full queue.

Implementation

As libnds has not yet got defines for the full range of operations on the FIFO I created a fifo.h file that contains appropriate defines:

#define REG_IPCFIFOCNT  (*(vu16*)0x4000184)
#define REG_IPCFIFOSEND (*(vu32*)0x4000188)
#define REG_IPCFIFORECV (*(vu32*)0x4100000)

#define IPC_FIFO_SEND_EMPTY (1<<0)
#define IPC_FIFO_SEND_FULL  (1<<1)
#define IPC_FIFO_SEND_IRQ   (1<<2)
#define IPC_FIFO_SEND_CLEAR (1<<3)
#define IPC_FIFO_RECV_EMPTY (1<<8)
#define IPC_FIFO_RECV_FULL  (1<<9)
#define IPC_FIFO_RECV_IRQ   (1<<10)
#define IPC_FIFO_ERROR      (1<<14)
#define IPC_FIFO_ENABLE     (1<<15)

The demo program displays the contents of the control registers for the two CPU's:

  consolePrintf("ARM9 Fifo Registers:\n");
  consolePrintf("REG_IPCFIFOCNT:   %x\n", REG_IPCFIFOCNT);
  consolePrintf("REG_IPCFIFORCV:   %x\n", arm9_recv);

  consolePrintf("ARM7 Fifo Registers:\n");
  consolePrintf("REG_IPCFIFOCNT:   %x\n", arm7_fifo->cnt);
  consolePrintf("REG_IPCFIFORCV:   %x\n", arm7_fifo->recv);

The value of the 'recv' register is stored in a variable when it is read on an appropriate button press. As reading this register destructively modifies the queue (it removes the data from it) I can't read the actual register every VBlank.

The code to handle the key presses on the ARM9 side is pretty basic:

    scanKeys();
    // ARM9 Keys
    if((keysHeld() & KEY_R) && (keysDown() & KEY_UP)) {
      REG_IPCFIFOCNT = IPC_FIFO_ENABLE | IPC_FIFO_SEND_CLEAR;
    }
    if((keysHeld() & KEY_R) && (keysDown() & KEY_RIGHT)) {
      static int count = 0;
      REG_IPCFIFOSEND = ++count;
    }
    if((keysHeld() & KEY_R) && (keysDown() & KEY_DOWN)) {
      arm9_recv = REG_IPCFIFORECV;
    }

On the reading and writing of the registers on the ARM7 we need to handle the keypress on the ARM9 and send a message to the ARM7 telling it to store the value of the registers or read a value. The FIFO would be ideal for this but since we want to display it's values I use the same 'control' type structure used in tutorial six, except instead of playing sounds we read and write registers.

The code for this is held in command.h, command7.cpp and command9.cpp. The ARM7 commands are:

static void CommandFIFOInit()
{
  REG_IPCFIFOCNT = IPC_FIFO_ENABLE | IPC_FIFO_SEND_CLEAR;
}

static void CommandFIFOSend()
{
  static int count = 0;
  REG_IPCFIFOSEND = ++count;
}

static void CommandFIFORecv()
{
  arm7_fifo->recv = REG_IPCFIFORECV;
}

void CommandProcessCommands() {
  static int currentCommand = -1;
  
  while(currentCommand != commandControl->currentCommand) {
    Command* command = &commandControl->command[currentCommand];
    switch(command->commandType) {
    case FIFO_INIT:
      CommandFIFOInit();
      break;      
    case FIFO_SEND:
      CommandFIFOSend();
      break;      
    case FIFO_RECV:
      CommandFIFORecv();
      break;      
    }
    currentCommand++;
    currentCommand &= MAX_COMMANDS-1;
  }
}

And they are sent on keypresses from the ARM9:

void CommandFIFOInit()
{
  Command* command = &commandControl->command[commandControl->currentCommand];
  command->commandType = FIFO_INIT; 
  commandControl->currentCommand++;
  commandControl->currentCommand &= MAX_COMMANDS-1;
}

void CommandFIFOSend()
{
  Command* command = &commandControl->command[commandControl->currentCommand];
  command->commandType = FIFO_SEND; 
  commandControl->currentCommand++;
  commandControl->currentCommand &= MAX_COMMANDS-1;
}

void CommandFIFORecv()
{
  Command* command = &commandControl->command[commandControl->currentCommand];
  command->commandType = FIFO_RECV; 
  commandControl->currentCommand++;
  commandControl->currentCommand &= MAX_COMMANDS-1;
}

int main() {
    [...]
    // ARM7 Keys
    if((keysHeld() & KEY_L) && (keysDown() & KEY_UP)) {
      CommandFIFOInit();
    }
    if((keysHeld() & KEY_L) && (keysDown() & KEY_RIGHT)) {
      CommandFIFOSend();
    }
    if((keysHeld() & KEY_L) && (keysDown() & KEY_DOWN)) {
      CommandFIFORecv();
    }
    [...]
}

You'll notice the ARM7 code sets member variables of an arm7_fifo structure. This is a structure held in shared memory so that the ARM9 can access it. The code to define this is in transfer.h:

/* Quick and dirty code to allow the ARM7 to inform the ARM9 of the
   current values of the FIFO registers */
struct ARM7_FIFO {
  uint32 cnt;
  uint32 send;
  uint32 recv;
};

#define arm7_fifo ((ARM7_FIFO*)((uint32)(IPC)+sizeof(TransferRegion)+sizeof(CommandControl)))

The location of this shared structure is placed after the libnds TransferRegion and the CommandControl stucture I use to send commands to the ARM7.

One good thing about using the FIFO for this sort of thing in the future is we won't need to play around with shared memory offsets to do this sort of thing.

Building the Demo

The complete example program is 'fifo_demo1'. The ARM9 code is in arm9_main.cpp. It uses the console routines to print information similar to the first tutorial. The ARM7 code is in arm7_main.cpp. A Makefile file is supplied to build everything.

The complete source code is supplied in fifo_demo1.zip and you can download the fifo_demo1.nds and fifo_demo1.nds.gba files for running on the emulators or hardware.

Conclusion

This tutorial covered how to use the FIFO registers which I believe are new to the Nintendo DS. It presented a demo program that allowed sending and receiving data and showing the results on the registers involved.

Remember that this demo only used integers as queue items. You can use pointers to anything you want. Integers, characters, or pointers to arbitary structures. Ideally you'd probably want to come up with a protocol and use this structure to pass the protocol information around.

There is the ability to set interrupts to occur when the send queue is empty or the recv queue is not empty. This removes the need to poll the control register to check the status of the queue. This tutorial has already gotten quite long so a future tutorial will demonstrate how to do this as well as how to use the FIFO in a more 'real world' situation.

As always, any comments or suggestions are welcomed. See my contact details below.