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
editOur 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
editDMA 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;
}