N64 Programming/Memory mapping

CPU  Main Memory (RDRAM) (2)           CDROM (8)
 |        |                               |
 ------------------------------------------
    |                    |
 Registers (1)      Cartridge (ROM) (4)


Naturally, we'd like to keep our data in registers (1 clock cycle).

Having the CPU execute from CDROM would be slow (8 cycles). Even cartridge memory is faster.

Most likely, the game will copy the important code and data to RDRAM to decrease load times. And improve execution.

This is done via Direct-Memory Access (DMA), speedy transfer.

Note that you may also find self-modifying code since we're running from RAM.

Because the code is run off of memory, it is compiled from RAM and not ROM addresses.

Think of 8-bit NES/SMS/GB page offsets.

Examples

edit
[0361:d824] 8002A14C: AND     k1[0000FF03],k1[0000FF03],at[FFFF00FF]
[0369:d825] 8002A150: OR      k1[00000003],k1[00000003],t1[0000FF00]
[3c09:a430] 8002A154: LUI     t1[0000FF00],FFFFA430h

is at ROM $554C (Fushigi no Dungeon 2).

Memory map

edit

Our memory map goes from $0000:0000-FFFF:FFFF.

COP0 can touch $2000:0000 upwards. Meaning it can change the physical addresses pointed at 24-bit offsets (16 MB). Or 8MB down to 4KB pages.

Generally,

$0000:0000 = ROM.
$1000:0000 = ROM.
$8000:0000 = RDRAM. Code.
$A000:0000 = RDRAM. Data.
$A400:0000 = PI,SI. DMA registers.
$B000:0000 = ROM (DMA, LD).

You'll see this as 'Translation Look-aside buffer' (TLB).

PI DMA

edit

DMA is a function of all modern systems to quickly move data between different components without involving the CPU. It is important to note that PI DMA memory transfers are done in 64-bit blocks. We can DMA from:

  • RDRAM to/from Cartridge (ROM,SRAM,FlashRAM,..)
  • RDRAM to/from RCP

DMA registers:

$A460:0000 = RAM address (address & 0x00FFFFFF)
$A460:0004 = ROM address (address & 0x1FFFFFFF)
$A460:0008 = Transfer size (from RAM to cartridge)
$A460:000C = Transfer size (from cartridge to RAM)
$A460:0010 = DMA Status

The last two addresses accept the DMA copy length (minus 1--BPL loop) and start the transfer. Which length register you write to depends on the transfer direction you want. Once the transfer has been initiated, the status of the transfer can be checked by reading the status register. The following flags can be used to check the status:

  • DMA_BUSY = 0x00000001
  • DMA_ERROR = 0x00000008

Below is some example C code to utilize the N64's DMA functions:

/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ **
** N64 DMA                         **
** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */

typedef struct
{
	/* Pointers to data */
	void *ramp;
	void *romp;
	
	/* Filesizes (8-byte aligned) */
	u32 size_ramrom; /* RAM -> ROM */
	u32 size_romram; /* RAM <- ROM */
	
	/* Status register */
	u32 status;
} DMA_REG;

/* DMA status flags */
enum
{
	DMA_BUSY  = 0x00000001,
	DMA_ERROR = 0x00000008
};

/* DMA registers ptr */
static volatile DMA_REG * dmaregs = (DMA_REG*)0xA4600000;

/* Copy data from ROM to RAM */
int dma_write_ram ( void *ram_ptr, void *rom_ptr, u32 length )
{
	/* Check that DMA is not busy already */
	while( dmaregs->status & DMA_BUSY );
	
	/* Write addresses */
	dmaregs->ramp = (u32)ram_ptr & 0x00FFFFFF; /* ram pointer */
	dmaregs->romp = (u32)rom_ptr & 0x1FFFFFFF; /* rom pointer */
	
	/* Write size */
	dmaregs->size_romram = length - 1;
	
	/* Wait for transfer to finish */
	while( dmaregs->status & DMA_BUSY );
	
	/* Return size written */
	return length & 0xFFFFFFF8;
}