N64 Memory

From en64 wiki
Jump to navigation Jump to search

  You either have the following two kinds of memory: read-only and read-write. Along with that, memory of course contains bytes, and those bytes are either instructions or data! One of the major components of the Nintendo 64 is the Rambus Dynamic Random-Access Memory (RDRAM). There are two of these bad boys in the Nintendo 64, and are often referred to as the "main memory" due to the presence of other memory chips used to store data for other various reasons, and at the hardware level, pretty much every other component make references to physical DRAM addresses!
  The N64 followed the design heritage of SGI supercomputing during the time it was developed and is one of the first modern consoles to implement a unified memory architecture (UMA), instead of having separate banks of memory for CPU, audio, and video, for example. One neat feature of the RDRAM is that it can also fill in the role to be the frame buffer, the Z buffer, or the audio buffer instead of just storing data. Each of the two RDRAM chips provide a memory map of 2 megabytes by 9-bits (9th bit is normally used for anti-aliasing and z-buffering and is normally "hidden"). for a total memory size of 4 megabytes by 9 bits, and as stated above, the Nintendo 64 was designed around the ability of the CPU, RSP and RDP being able to share this memory. Both of the RDRAM chips can be found directly between the RCP and jump pack/expansion slot on the motherboard.

N64 Memory specifications:

  • 4 MB RDRAM on a 9-bit data bus at 500Mhz | Peak Bandwidth: 562.5 MB/s
  • Extendable to eight megabytes with Expansion Pack

Memory Map Overview

Every RCP register (SP to SI) is 1MB in size, but the valid address space of accessible registers is far shorter.

0x00000000 to 0x03EFFFFF RDRAM Memory
0x03F00000 to 0x03FFFFFF RDRAM Registers
0x04000000 to 0x040FFFFF SP Registers
0x04100000 to 0x041FFFFF DP Command Registers
0x04200000 to 0x042FFFFF DP Span Registers
0x04300000 to 0x043FFFFF MIPS Interface (MI) Registers
0x04400000 to 0x044FFFFF Video Interface (VI) Registers
0x04500000 to 0x045FFFFF Audio Interface (AI) Registers
0x04600000 to 0x046FFFFF Peripheral Interface (PI) Registers
0x04700000 to 0x047FFFFF RDRAM Interface (RI) Registers
0x04800000 to 0x048FFFFF Serial Interface (SI) Registers
0x04900000 to 0x04FFFFFF Unused
0x05000000 to 0x05FFFFFF Cartridge Domain 2 Address 1
0x06000000 to 0x07FFFFFF Cartridge Domain 1 Address 1
0x08000000 to 0x0FFFFFFF Cartridge Domain 2 Address 2
0x10000000 to 0x1FBFFFFF Cartridge Domain 1 Address 2
0x1FC00000 to 0x1FC007BF PIF Boot ROM
0x1FC007C0 to 0x1FC007FF PIF RAM
0x1FC00800 to 0x1FCFFFFF Reserved
0x1FD00000 to 0x7FFFFFFF Cartridge Domain 1 Address 3
0x80000000 to 0xFFFFFFFF External SysAD Device

Memory Map Detailed

CPU Addressing


The main R4300i processor has a huge role in deciding where memory is placed and how it is placed through the Control processor (CP) which acts as a coprocessor. The CP is composed of: the Translation Lookaside Buffer and a Memory Management Unit (COP0). The Nintendo 64 is a single user system, meaning, it normally just runs in one kernel mode. All addresses related to the CPU are NOT physical addresses, but are always virtual addresses. The kernel can be either in 32 or 64-bit addressing mode, but usually for performance reasons it's 32-bit, and the manner in which memory addresses are translated or mapped depends on the operating mode of the CPU. To try to develop a correct mental image with mode transitioning, if an exception occurs, the CPU enters the Kernel mode and stays in this mode until it receives an exception return instruction (ERET). The following is a table for virtual address translation for 32-bit kernel addressing mode (mapped meaning mapped through the MMU):

  • 0x80000000 - 0x7fffffff KUSEG, USEG, SUSEG TLB map (User Mode)

Accessible in all modes

  • 0x80000000 - 0x9fffffff KSEG0 Direct map (Cached Memory)

Accessible in kernel mode, not mapped

  • 0xa0000000 - 0xbfffffff KSEG1 Direct map (Non-cached Memory)

Accessible in kernel mode, not mapped

  • 0xc0000000 - 0xdfffffff KSSEG, SSEG TLB mapping (Supervisor Mode)

Accessible in kernel or supervisor mode

  • 0xe0000000 - 0xffffffff KSEG3 TLB mapping (Kernel Mode)

Accessible in kernel mode

Kernel Operation Modes
The only difference between KUSEG, KSSEG, and KSEG3 is the virtual address space. KUSEG is the "user" address space accessible from kernel mode (this is the same address space accessible from user mode). KSSEG is the "supervisor" address space (same address space as accessible in supervisor mode), and KSEG3 is one of the three address spaces accessible only from kernel mode (and the only segment of the three that is mappable via TLB).

  • User Mode: In the single-user mode, a virtual address space of 2 GB can be used in the 32-bit mode. (Will explain more.)
  • Supervisor Mode: Will explain.
  • Kernel Mode: Will explain.

Cached and Non-cached Memory
In attempt to create a proper frame of mine around cached and non-cached memory: Even if a game is allocated to the uncached memory of kernel mode KSEG1, data is still present in it's cached counterpart KSEG0. By that I mean, they point to the same physical address; all writes between locations are accessible by each other. You obtain performance gains by using non-cached memory, but only in tight loops (a loop that iterates many times/plays an important role in the games performance). Like not having to flush the CPU cache when running a lot of cycles in a short time. The only time you care about cache is when writing self-modifying code, or when doing silly interleaved access between each address space.
More information is covered in the N64_CPU and the N64_TLB pages.
Note, if you're ever feeling froggy or get a wild hair caught up your ass and want to make a really big TLB page, be sure to keep your peripherals away from your cached address space by placing them near the top of your address space.

Rambus DRAM/Physical Memory and Pinout

The tech jargon of physical memory has already been covered in the above introduction, for the most part, at least. Physical memory starts at 80000000 and ends at 803FFFFF for 4MB and 807FFFFF for 8MB. Nintendo 64's older than the sixth version use two 18MB chips while versions 6-9 use a single 36MB chip, and both are 9-bit wide. A cool Project 64 modification to allow upwards to 32MB.

Looking at the front of the console: pin 1 is back right, pin 32 is back left.
# Name
3 DQ8
5 DQ7
6 NC (18M) ; VREF (64M)
9 DQ6
10 GND
11 DQ5
16 VDD
17 DQ4
18 GND
20 SIN
23 DQ3
24 GND
25 DQ2
26 (NC)
27 DQ1
28 GND
29 DQ0
30 (NC)
31 GND
32 VDD

RDRAM specific features:

  • Compatible with base RDRAMs
  • 600MB/s peak transfer rate per RDRAM
  • Rambus Signaling Level (RSL) interface
  • Synchronous, concurrent protocol for block-oriented, interleaved (overlapped) transfers
  • 480MB/s effective bandwidth for random 32 byte transfers from one RDRAM
  • 13 active signals require just 32 total pins on the controller interface (including power)
  • 3.3 V operation
  • Additional/multiple Rambus Channels each provide an additional 600 MB/s bandwidth
  • Two of four 2KByte sense amplifiers may be operated as caches for low latency access
  • Random access mode enables any burst order at full bandwidth within a page
  • Graphics features include write-per-bit and mask-per-bit operations
  • Available in horizontal surface mount plastic package (SHP32-P-1125-0.65-K)

Example Timing Charts

A T-state is one subdivision of the operation performed in one clock period and each T-state is precisely equal to one clock period. These subdivisions are internal statate of the microprocessor precisely synchronized with the system clock; equal to one clock period. I highly doubt anyone will be repairing or swapping out RAM, so I don't think I need to take the time to cut down on the jargon in this subcategory to help individuals out. If they were taking up a job like this, they'd already know.

As a note: page mode, is random access mode. Bank Active and Random Read Cycles Within a Page

Bank Active and Random Write Cycles Within a Page

Interleaved Read Transaction Example and more RDRAM information (as well as the pin descriptions), go here!

Boot Segment and Process

  The following is a fair amount of information relating to the boot segment which is universal between games. The CIC 6103 and 6106 lockout chips can falsify boot locations/entry points: For things related to the boot chip (CIC) go here. Anyways, the entry point/boot location is not hardcoded and can be changed by inserting an address statement in the "boot segment" of the makerom() specification file. In relation to entry points, they can be wherever as long as the location isn't reserved for something else whose address is aligned to a multiple of 32 for instruction cache alignment.

Boot Procedure

Initialize CPU CP0 regs -> then RCP -> then RDRAM and CPU caches -> Load 1MB to 0x400 -> Clear RCP Status -> Execute game code

Boot Strap

Additional Resource

  Okay, so I apologize in advance for how messy this category is, but something is better than nothing. I think it's safe to note that the addresses in the table below aren't the same for TLB mapped games. The Nintendo 64's bootstrap is not well documented, to my knowledge at least, and can be quite complex. With that said, I'd really love input on the forums about how I could better this section. Thank you.

  PIFrom sends data to, as well as starts executing code from 0xBFC00000, (Cold Reset, Soft Reset, and NMI exceptions are vectored to this address in 32-bit mode) after the PIF releases an NMI on the CPU. Essentially, the PIF ROM boots the n64 and validates (first 0x1000 bytes of the ROM) that the ROM has a valid bootcode (CIC) and checksum. At 80000300 the bootstrap will copy some values returned by the PIF (Peripheral Interface) and CIC during startup to 80000300.
Part of PIF BootROM executed in RSP IMEM just before clearing IMEM & DMEM, and jumping to game entrypoint/Devise state flags below:

An asterisk (*) denotes a note

Address Bytes Name/Code Register Notes
typedef struct { Not displayed below is: $S6 which is CIC seed value
80000300 4 osTvType
u32_t tv_type;
$S4 1 = NTSC, 0 = PAL, 2 = MPAL (hardcoded in PIF BootROM)
80000304 4 osRomType
u32_t rom_type;
$S3 0 = GamePack, 1 = DD
80000308 4 osRomBase
u32_t rom_base;
N/A base address for cart ROM or IPL (0xB0000000 for GamePack, 0xA6000000 for DD)
8000030C* 4 osResetType
u32_t reset_type;
$S5 0 = ColdReset, 1 = NMI (an exception), 2 = reset to boot disk
80000310* 4 osCicId
u32_t cic_id;
N/A CIC6103 writes 6103, CIC6106 writes 6104, CIC6105 writes 6105, CIC6102 & CIC6101 do not write anything
80000314 4 osVersion
u32_t version;
$S7 00 = 1.0, 15 = 2.5, etc
80000318 4 osMemSize
u32_t mem_size;
N/A RDRAM size (specify the amount of detected rdram 4Mo or 8Mo usually)
8000031C* 40 osAppNMIBuffer
N/A small buffer for stuff to retain through reset; initialized first run
} os_boot_config_t; From here, If you're using an emulator that utilizes PIFrom. Then PIFrom automatically passes control to the bootstrap, and this in turn passes

control to the main game executable.


  • Games using the CIC 6105 chip rely on the state of the t3 and sp registers for decrypting stuff.
  • 8000030C-80000310: Spoken about in the CIC link above!'
  • 8000031C: u8_t app_nmi_buffer[64]; <~ with the space included is the actual code. I had to remove the space for formatting reasons

  0x80000400 (physical address 0x400) is by default the boot location. All R4xxx series processors uses the first 0x200 of RAM for exception handlers. The first 0x400 of physical memory contains the Interrupt Vector Table (and first 0x180 are the Processor Exception Vectors) and configuration parameters. The "interrupt vector table" is a data structure that associates a list of interrupt handlers with a list of interrupt requests. Put simply, an interrupt and exception handler both alter program flow. Interrupts handle external event, e.g. cartridge issue (peripheral interrupt), Pre-nmi (reset type), etc. Exceptions handle instruction faults, e.g. dividing by zero, etc. One thing to note about interrupts and exceptions with the Nintendo 64 that the R4300i architecture doesn't distinguish between them as there's essentially a single handler for them and the two merely have unique masks. However, in applications outside of this situation, they have individual handlers.
  Along with that, the CIC chip can relocate the boot location/entry point (which I linked to information relating to this initially at the top of this category). You can locate the start of the code segment by going to 0x8 (a word long) in any Nintendo 64 ROM, copying the address and breakpointing (linebreaking) it (as explained further down the page). The word found there is a pointer to the start of the real game code (Where memory officially starts). Everything before that is pretty much bootstrapping related and is for the most part, the same between games. For games that make use of the 64DD keep a copy of the original disk's disk header at 800001A0 for reference.

Exception Vectors

Main page
  Four exceptions are hardcoded (some locations dependent on status flag state): 0x80000000 is for TLB miss, 0x80000080 XTLB miss (64-bit), 0x80000100 for cache errors, and 0x80000180 for general exceptions (including caught interrupts). In addition to 0x80000180 the exception vector is here and can be used as a place to hook (to execute every frame) for Nintendo 64 GameShark codes ( made in assemby). However, a con to that is your code probably won't work on an actual GameShark as it hooks with a similar method (described below). In practice these are all just redirects to the general exception handler. Just a note about the exceptions: COP0 says why an exception occurs at a given point in time, but the vector table is the address where the CPU will jump to when said exception occurs/where the CPU goes to handle an exception.
Speaking of which, the GameShark works by relocating the general exception handler to 0xA0000120 (uncached memory) and hooking GameShark utilities into the "regular" general exception handler at 0xA0000180 during which point in the execution path, a "code generator" gets executed. The code generator is basically an engine made up of instructions which act on behalf of GameShark codes, usually to make data at an address be written with a chosen value over and over again every time an interrupt occurs. Note, the GameShark writes to both cacheable and non-cachable memory (For a better understanding, read "CPU Addressing above").

Memory/Data Segments

  I figured it is worth mentioning before typing this up: I'm not very sasvvy when it comes to the Nintendo 64 and general memory segmentation. With that said, input is not only appreciated, but needed! Usually, there are four types of memory segments used in programs that utilize memory segmentation. The text and data segments are static while the game is running, on the other hand, the stack and heap segments can grow and shrink during run-time to adequately allocate memory. Memory segmentation is simply sectioning or segmenting a games memory; each segment is used to contain a specific type of data and serve an individual purpose. Moreover, you can have more than merely one of each segment. The "odd" thing with Nintendo 64 games though, is that there isn't really an individual data and text segment. Well, there are, and there aren't. Normally, a text/code segments contain the instruction to carry out operations, and the data segments hold the variables for the instructions. With that said, for example, you can quite obviously see .text and .data segments in Ocarina of Time, but in some places they're more of a hybrid. By that I mean, having instructions and data in the same segment...

  The Nintendo 64 operating system doesn't impose a default memory allocation scheme or anything of that nature between games. The number of segments and where they're located is entirely up to the game developers (just like with TLB mapping and kernel modes). However, there is always one stack per CPU thread, and one main heap (I think?). Besides that, the only statically allocated resources would be the audio playback (the sounds themselves aren't) and the I/O device access. It is however important to note that since the stack and main heap segments are ever expanding and shrinking, it is pretty self explanatory that the code and data segments are always in low memory and coupled together. For the sake of including it: below I'll go ahead and separate the text and data segments as they are separate in some games and function as they would in other applications outside the Nintendo 64.


  The text or "Code" segment is represented by the .text section and contains all the machine code/instructions for Nintendo 64 games. Essentially, it instructs/executes the steps carried out by the functions. For example: Actor, object, entrance, text and scene data tables are located here.


  The data segment contains all the static data like for variables during program execution. The segment is represented by the .data and .bss (basic service set) sections: The .data section is used to declare the memory region, where data elements are stored and and is static, while the .bss section is also a static memory section that contains buffers for data to be declared later (the buffer data is filled with zeros).


  After execution, a certain adjacent(neighboring) section of memory is set aside for the program; this is the stack! The stack follows a Last In First Out (LIFO) storage device/system/access policy, and is used for temporary storage. The stack is composed of the stack pointer, limit, and bottom. The smaller (valid) addresses are at the limit (the top), the larger (valid) addresses are located at the bottom (as the name suggests, the bottom of the stack). The stack pointer contains the smallest address and is normally a register the top of the stack, or, "points" to where the top of the stack is, if you will. Anything above the stack pointer (SP) is considered garbage, anything below is considered valid! Back to LIFO policy: Push and Pop are the only two operations of the stack.

  • push: You can push one or more registers at a given time by setting the stack pointer to a smaller value. That is usually done by subtracting four times the number of registers to be pushed onto the stack.
  • pop: You can pop one or more registers by copying the data from the stack to the registers, then add a value to the stack pointer (The opposite of push, usually by adding four times the number of registers to be popped on the stack)

More information the stack


  VRAM encompasses this segment and subfiles such as overlays are located here. Put simply, arbitrarily allocated data resides here as a memory management technique. For example, global context, object and scene files are allocated here.

Direct Memory Access (DMA)

  DMA, as the name suggests is used to access other hardware components by means of accessing or transferring data to and from main memory independently from the CPU. It is a very effective way to transfer a lot of data, and gives the CPU the advantage to carry out other processes until the data transfer ends (as noted in the prior sentence)! However, the operators of the DMA are still initiated by the CPU; the DRAM addresses passed to the interface routines is CPU VRAM. The said routines carry out the mapping from virtual to physical addresses and give the resulting physical address to be suitable hardware registers. Jargon wise: The DMA is initialized by the host processor (the CPU) with a count of the number of words to transfer, the memory address to use, then does other processes while the transfer is in progress and will eventually receive an interrupt (signal) from the DMA controller when the process is finished. Moreover, the DMA controller can generate memory addresses and initiate memory read or write cycles. It contains several processor registers that can be written and read by the CPU. Note: All DMA transactions in the Nintendo 64 must use 64 bit aligned for data in RDRAM. DMA transactions for data in ROM must use 16 bit aligned addresses.

An example of the usefulness of the DMA: RSP DMA transfer rate http://hcs64.com/dma.html

PI (Parallel Interface)

More on the Peripheral interface.
The PI is the 16-bit parallel bus connection the N64 Game Pak or the N64 Disk Drive. The average transfer rate for the Game Pak is about 5 megabytes per second. (The peak performance is about 50 megabytes per second.) The PI uses DMA transfer to move information from the Game Pak or N64 Disk Drive to RDRAM (data accesses the ROM through the PI registers).

Memory Management Unit and Virtual RAM (VRAM)

N64 games can address 8 MiB of virtual ram and encompass: 80800000 to 80FFFFFF
  Physical memory can be mapped to VRAM by the TLB or framebuffers. VRAM is the space beyond the actual, physical memory and is supported by the Memory Management Unit (MMU). In relation to hacking, the main issue with locating "dynamic memory" is that the Nintendo 64 itself does not have a default/static location to allocate the arbitrary data to as it varies from game to game as noted above in the memory segment category and it's location on the memory map is reliant on the kernel mode. If the called data is in physical memory it is used as-is, but from the point of view of the user, it seems like all one big memory space. The purpose of VRAM is that it allows the compiler to allocate more space than is available, so that the sub programs/files (stored in overlay files) for actors and similar are able to have their own addressable space without needing to take up the physical RAM space. Moreover, the MMU greatly reduces the speed at which a game runs when virtual mapping a lot of addresses, thus a lot of game developers went the physical memory route.

Translation lookaside buffer (TLB) and Related Misc

  Most Nintendo 64 games you'll come across probably won't utilize the TLB too much and will "directly" map address (although not actually direct). It mostly comes down to if whatever game needs more address space or not. In situations that the game doesn't, the programmers will locate addresses between KSEG0 and KSEG1. As noted in the manual, for situations like this, the physical address is calculated by subtracting the base address from the virtual address. The TLB maps virtual addresses into physical addresses, but is better described as: it's a cache for the virtual address to physical address lookup (greatly increases the speed at which the addresses can be retrieved in comparison to RAM). The TLB's page tables (Covered in the link below) provide a way to map virtual address to physical address, by looking up the virtual address in the page tables, and the max table size is 4KB and the TLB supports up to 32 entries (each of which maps two physical pages). An analogy would be having a pen on your desk instead of having to navigate down stairs to access it to write a paper. The pen being on your desk would be cache and it's location downstairs would be memory! The TLB is also used to improve the speed of virtual address translation. Now, virtual address translation is "The process of mapping virtual address space into physical address space."
  With having all that squared away, more simply put, the translation lookaside buffer is a memory cache that stores recent translations of virtual memory to physical memory to be retried quicker in comparison to a hard drive or with memory. Some games make more use of the TLB than others such as GoldenEye and Perfect Dark. The only down side to us hackers is that it makes it very difficult to create advanced GameShark codes with assembly due to address aliasing, which is a bummer. For more information, click the link below!
Click me!

Operating System

The developers of the Nintendo 64 manual created it under the assumption that you have every SDK in tact, which is not the case with any of us here. Included along with that, they made it under the assumption that you understand the programmer jargon. N64 games are all made with the same devkit, and all run in a simple multithreaded OS and each thread has it's own individual stack. The CPU executes threads, and the RCP executes tasks.. They are almost entirely coded in C.
For more information

ROM-to-RAM tracing

(And vise versa)
  Usually needlessly, unlike a lot of other platforms, Nintendo 64 games have a lot of compressed data which can create a lot of problems for would-be hackers. One important thing to note is that most of the time games use the TLB to convert physical addresses to virtual which can be arduous when tracing to ROM pretty much all the time.
With that said, I'm all about expanding knowledge for identical causes, so I took the liberty of displaying notes below for tracing RAM addresses to ROM and vise versa that Twili was so gracious to teach me. I will also go into detail about how to locate the start and end of the code segment in ROM! Note: Not all Nintendo 64 games use the same "code" file (again, as noted above). Nonetheless, "standard" functions are always in lower memory. By "standard functions," I mean functions that are within the code/text segment's within RAM. As stated in the "Memory Segment" category, things in higher memory are almost always arbitrarily located as well as mapped there through the TLB which makes it incredibly difficult to trace back to ROM.

Locating the start and end of a code segment

  • To locate the start of the code/text segment's in RAM, you can do this one of two ways:
1.) If you're using Nemu64, you can utilize the "Show Rom Info" option I spoke of above to obtain the RAM entry point, breakpoint the address (which is 
a pointer) of the entry point and it will take you to where the standard (libultra) functions start! 
2.) Moreover, you can open the ROM of your choice in a hex editor and navigate only 0x8 bytes in and copy the word found there. (A word is 32-bits long 
  • To locate the end of the code/text segment's in RAM: First, as also explained below, open a hex editor of your choice and navigate to 0x1000 and scroll down until the machine code ends, and take note of the address that it ends on as that is the end offset of the standard functions. Then: (End offset in ROM + RAM entry point) - 0x1000
  • To trace a target RAM address from a MIPS JAL instruction in this segment:
Discovered RAM Address = (JAL instruction & 0xffffff) << 2
(Discovered RAM Address - RAM entry point) + 0x1000

Method to locating alternative code segments

  • In order to locate a game's compiled main.c file, open up a hex editor and search for the ASCII text "RSP Gfx" as all ROMS have that string to specify what Microcode Library the game uses. If the game has a file table, then the ROM search will put you between the start and end address of a file table entry. That string is always found in the main code segment!
  • If you've located the code segment, record the start address of it from the file table. Then, you must determine the RAM start address. See the tutorial on this page for methods.
  • To get the RAM end address, simply subtract the start address from the ROM's file table from the end address from the ROM's file table. Then, add the difference to the RAM start address.
  • If you have the ROM start address, RAM start address, and RAM end address, and have discovered code/text in RAM within the segment:
(Discovered RAM Address - code segment RAM start address) + code segment ROM start address
  • To locate ROM to RAM, just swap RAM for ROM in the equation above: (Discovered ROM Address - code segment ROM start address) + code segment RAM start address

Note: I've only noticed yaz0 games having file tables, and the ones outside of that that also had them are: Super Smash Bros and Star Fox 64.

For locating VRAM code/text segments: Depending on where the addresses are located (as noted above), and how "generous" the game developers were, some games make use of the TLB as a mapping method. See this page for methods of locating VRAM addresses in RAM and ROM.

Virtual ROM (VROM)

  In commercial releases, the majority of the game files are compressed so that the game is able to fit on the limited space of the 32 MiB rom. VROM addresses are thus used to represent the space that the file would be located if none of the files were compressed.

Reality Co-Processor


Save Types

EEPROM and shit