The following diagram illustrates the CPU memory map
+-------------+ 0x10000
| PRG-ROM |
| Upper Bank |
|- - - - - - -| 0xC000
| PRG-ROM |
| Lower Bank |
+-------------+ 0x8000
| SRAM |
+-------------+ 0x6000
|Expansion ROM|
+-------------+ 0x4020
|I/O Registers|
|- - - - - - -| 0x4000
| Mirrors |
|- - - - - - -| 0x2008
|I/O Registers|
+-------------+ 0x2000
| Mirrors |
|- - - - - - -| 0x0800
| RAM |
|- - - - - - -| 0X0200
| Stack |
|- - - - - - -| 0x0100
| Zero Page |
+-------------+ 0x0000
Unlike many CPUs that have a group of registers, like CHIP8 that has 16 registers, the NES cpu does not have them, instead it has:
- One accumulator (A)
- Two indexes (X and Y)
- One program counter (PC)
- One stack pointer (S)
- One status register (P)
Definition | Size (bits) |
---|---|
A | 8 |
X | 8 |
Y | 8 |
PC | 16 |
S | 8 |
P | 8 |
The accumulator is an 8-bit register that stores the results of arithmetic and logic operations.
The X and Y registers are both 8-bit.
The program counter is a 16-bit register wich holds the address of the next instruction to be executed.
The stack is located between the memory locations 0x0100 to 0x01FF ( This can be visualized in the diagram CPU Memory Map).
The stack pointer is an 8-bit register, the stack works top-down wich means that when a byte is pushed the stack pointer is incremented. There is no overflow, the stack pointer will wrap around from 0x0100 to 0x01FF.
The stack pointer allways points to the next free location.
Six of the eigth bits of the status register (P) are used by the arithmetic logic unit (ALU).
The 8-bits of the status register follow structure:
+-+-+-+-+-+-+-+-+
|N|V| |B|D|I|Z|C|
+-+-+-+-+-+-+-+-+
7 6 5 4 3 2 1 0
Description:
C - Carry Flag
Z - Zero Flag
I - Interrupt Disable
D - Decimal Mode
B - Break Command
V - Overflow Flag
N - Negative Flag
Zero page addressing uses a single operand wich serves as a pointer to an address in zero page (0x0000 to 0x00FF).
Example:
Instruction: AND 0x12
A &= RAM[PC + 1];
...
PC += 2;
| |
+--------+
| AND | PC <-- Current Opcode
+--------+
| 0x0012 | PC + 1 <-- The memory location of the value
+--------+
| ...... | PC + 2 <-- The next Opcode
+--------+
| |
Indexed zero page addressing takes a single operand and adds the value of a register to calculate the zero page address.
There are two forms of indexed zero page addressing:
-
Zero Page, X - Add contents of X register to operand.
-
Zero Page, Y - Add contents of Y register to operand.
The wrap technique is used to assure that the sum will not exceed the zero page addresses. In other words, 0x0001 + 0x00FF = 0x0000 instead of 0x0100.
Example:
Instruction: AND 0x12, X
A &= RAM[PC + 1 + X];
...
PC += 2;
| |
+--------+
| AND | PC <-- Current Opcode
+--------+
| 0x0012 | PC + 1 <-- The memory location of the value
+--------+
| ...... | PC + 2 <-- The next Opcode
+--------+
| |
In absolute addressing, the address of the data to operate on is specified by the two operands supplied, least significant byte first.
Example:
Instruction: AND 0x1234
address = (RAM[PC + 2] << 8) | RAM[PC + 1];
A &= RAM[address];
...
PC += 3;
| |
+--------+
| AND | PC <-- Current Opcode
+--------+
| 0x0034 | PC + 1 <-- The least significant byte
+--------+
| 0x0012 | PC + 2 <-- The most significant byte
+--------+
| ...... | PC + 3 <-- The next Opcode
+--------+
| |
Indexed absolute addressing takes two operads, forming a 16-bit address (just like absolute addressing). The difference is that we sum the value of a register to calculate the final address.
There are two forms of indexed absolute addressing
-
Absolute, X - Add contents of X register to operand.
-
Absolute, Y - Add contents of Y register to operand.
Example:
Instruction: AND 0x1234, X
address = (RAM[PC + 2] << 8) | RAM[PC + 1];
A &= RAM[address + X];
...
PC += 3;
| |
+--------+
| AND | PC <-- Current Opcode
+--------+
| 0x0034 | PC + 1 <-- The least significant byte
+--------+
| 0x0012 | PC + 2 <-- The most significant byte
+--------+
| ...... | PC + 3 <-- The next Opcode
+--------+
| |
Indirect addressing takes two operads, forming a 16-bit address (just like absolute addressing). This address is then used to calculate the real value.
Example:
Instruction: JMP ($1234)
address = (RAM[PC + 2] << 8) | RAM[PC + 1];
real_value = (RAM[address + 1] << 8) | RAM[address];
JMP RAM[real_value]
PC += 3;
| |
+--------+
| AND | PC <-- Current Opcode
+--------+
| 0x0034 | PC + 1 <-- The least significant byte
+--------+
| 0x0012 | PC + 2 <-- The most significant byte
+--------+
| ...... | PC + 3 <-- The next Opcode
+--------+
| |
+--------+
| 0x56 | 0X1234 <-- Least significant byte of the real value
+--------+
| 0x78 | 0x1235 <-- Most significant byte of the real value
+--------+
| |
PPU has two control registers that are in the memory locations 0x2000 (PPU Control Register 1) and 0x2001 (PPU Control Register 2)
+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0|
+-+-+-+-+-+-+-+-+
| | | | | | | |
| | | | | | | +---> If 1 then color mode, else monochromatic mode
| | | | | | +----->
There are a few type of formats, iNES, NES2 and others.
The Nintendo NES has two ports, each one of them as a unique address.
- Port 1: 0x4017
- Port 2: 0x4016
When there was a four-player adapter the addresses looked like:
- Port 1: 0x4017
- Port 2: 0x4016
- Port 3: 0x4017
- Port 4: 0x4016
0000-07FF is RAM, 0800-1FFF are mirrors of RAM (you AND the address with 07FF to get the effective address) 2000-2007 is how the CPU writes to the PPU 2008-3FFF are mirrors of that address range. 4000-401F is for IO ports and sound 4020-4FFF is rarely used, but can be used by some cartridges 5000-5FFF is rarely used, but can be used by some cartridges, often as bank switching registers, not actual memory, but some cartridges put RAM there 6000-7FFF is often cartridge WRAM. Since emulators usually emulate this whether it actually exists in the cartridge or not, there's a little bit of controversy about NES headers not adequately representing a cartridge. 8000-FFFF is the main area the cartridge ROM is mapped to in memory. Sometimes it can be bank switched, usually in 32k, 16k, or 8k sized banks.
The NES header takes up 16 bytes, after that is the PRG pages, then after that is the CHR pages. You look at the header to see how big the PRG and CHR of the cartridge are, see documentation for more details. The NES header does not exist outside of .NES files, you won't find it on any NES cartridges.
So you load a Mapper 0 (NROM) cartridge into memory, and the first two PRG banks appear in NES memory at 8000-FFFF. If there is only one 16k bank, then it is mirrored at 8000-BFFF and C000-FFFF.
When the CPU boots up, it reads the Reset vector, located at FFFC. That contains a 16-bit value which tells the CPU where to jump to. The first thing a game will do when it starts up is repeatedly read PPU register 2002 to wait for the NES to warm up, so you won't see a game doing anything until you throw in some rudimentary PPU emulation. Then the game clears the RAM, and waits for the NES to warm up some more. Then the system is ready, and the game will start running.