Building a NES emulator
Why make another emulator?
Why make another emulator?
It's fun to do! ™
NES + Presentation
NES
Presentation
NES
Presso
"If you're looking for a good first game for your new emulator, try anything made in 1984 or earlier, such as Donkey Kong."
We are only interested in emulating Donkey Kong (1983).
Keep things simple;
only implement the most critical functionality to get something on the screen.
Ignore input/gamepad (DK has a "demo mode" if left idle).
No audio, certainly not enough time... :)
Goals
Project Setup
Project Setup
GENie build system
https://github.com/bkaradzic/GENie
C++ish, ie sane non modern C++
https://gist.github.com/bkaradzic/2e39896bc7d8c34e042b
Project Setup
Third party libraries
- minifb - Blitting pixels, only need a framebuffer and a window.
Info, NES spec, 6502 datasheet etc;
https://wiki.nesdev.org/
A NES frame
Background
Sprites
+
=
256 (32 x 8)
240 (30 x 8)
Backgrounds in NES; stored in "nametables" in the Video RAM (VRAM)
Grid of integers, identifying what tile to show in each cell.
256 (32 x 8)
240 (30 x 8)
Each cell is 8 x 8 pixels
To find what pixels to show in each cell,
we need to find the "tile" in the game ROM.
iNES header;
meta data
PRG ROM;
Game code
CHR ROM;
Tiles used for background and sprites
Nametable (Background)
Tiles from CHR ROM
0
16
32
48
64
80
96
112
128
144
160
176
192
208
224
240
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
0
16
32
48
64
80
96
112
128
144
160
176
192
208
224
240
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Nametable (Background)
Tiles from CHR ROM
0
16
32
48
64
80
96
112
128
144
160
176
192
208
224
240
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Nametable (Background)
Tiles from CHR ROM
0
16
32
48
64
80
96
112
128
144
160
176
192
208
224
240
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Nametable (Background)
Tiles from CHR ROM
0
16
32
48
64
80
96
112
128
144
160
176
192
208
224
240
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Nametable (Background)
Tiles from CHR ROM
Palettes
Palettes
Tiles stored in the CHR ROM don't have color information.
One 8x8 pixel tile is stored as 16 bytes, 2 bytes per "row", ie two bits per pixel.
(Compare this with a "modern" image;
3, or 4 for alpha, bytes per pixel.)
Palettes
First row;
Byte 1: 5
Byte 2: 7
Palettes
First row;
Byte 1: 5
Byte 2: 7
0 0 0 0 0 0 1 0 1
0 0 0 0 0 0 1 1 1
00 00 00 00 00 00 11 10 11
Palettes
First row;
Byte 1: 5
Byte 2: 7
0 0 0 0 0 0 1 0 1
0 0 0 0 0 0 1 1 1
00 00 00 00 00 00 11 10 11
Palettes
First row;
Byte 1: 5
Byte 2: 7
0 0 0 0 0 0 1 0 1
0 0 0 0 0 0 1 1 1
00 00 00 00 00 00 11 10 11
Palettes
First row;
Byte 1: 5
Byte 2: 7
Palettes
These values correspond to palette values.
2 bits per pixel means 4 different colors.
Sprites work the same way, but here we can only have 3 different colors since the value 0 means transparent!
Now we "kinda" know what tiles are and how they are stored...
But "something" needs to set these tiles as sprites and nametables/backgrounds?
That "something" is the game code, so how do we run this code?
NES Hardware
Pt 1
NES Hardware
NES Hardware
CPU
PPU
RAM
VRAM
Cartridge Slot
NES Hardware
CPU
PPU
RAM
VRAM
Cartridge Slot
CHR data (sprites and background)
PRG data (game code/logic)
PRG and CHR data stored on cartridge
NES Hardware
CPU
PPU
RAM
VRAM
Cartridge Slot
CHR data (sprites and background)
PRG data (game code/logic)
PRG and CHR data stored on cartridge
NES Hardware
CPU
PPU
RAM
VRAM
Cartridge Slot
CHR data (sprites and background)
PRG data (game code/logic)
PRG and CHR data stored on cartridge
NES Hardware
CPU
PPU
RAM
VRAM
Cartridge Slot
NES Hardware
CPU (and APU)
PPU
RAM
VRAM
Cartridge Slot
CPU
NES Hardware
CPU (and APU)
PPU
RAM
VRAM
Cartridge Slot
CPU Simulation
while true:
instr = get_next_instruction(GAME_ROM)
execute(instr)
NES Hardware
CPU (and APU)
PPU
RAM
VRAM
Cartridge Slot
CPU Simulation
fn execute(instr):
switch (instr)
case LDA:
// load register A...
case JMP:
// jump somewhere in the program
//....
while true:
instr = get_next_instruction(GAME_ROM)
execute(instr)
NES Hardware
CPU (and APU)
PPU
RAM
VRAM
Cartridge Slot
CPU Simulation
fn execute(instr):
switch (instr)
case LDA:
// load register A...
case JMP:
// jump somewhere in the program
//....
while true:
instr = get_next_instruction(GAME_ROM)
execute(instr)
// instructions above will update the VRAM
update_screen(VRAM)
NES Hardware
CPU (and APU)
PPU
RAM
VRAM
Cartridge Slot
CPU Simulation
fn execute(instr):
//....
fn update_screen(VRAM):
for y = 0..30:
for x = 0..32:
tile = VRAM[y*32+x]
blit_tile_to_screen(x, y, tile)
for sprite in sprites:
blit_sprite_to_screen(sprite)
while true:
instr = get_next_instruction(GAME_ROM)
execute(instr)
// instructions above will update the VRAM
update_screen(VRAM)
NES Hardware
CPU (and APU)
PPU
RAM
VRAM
Cartridge Slot
CPU Simulation
fn execute(instr):
//....
fn update_screen(VRAM):
for y = 0..30:
for x = 0..32:
tile = VRAM[y*32+x]
blit_tile_to_screen(x, y, tile)
for sprite in sprites:
blit_sprite_to_screen(sprite)
while true:
instr = get_next_instruction(GAME_ROM)
execute(instr)
// instructions above will update the VRAM
update_screen(VRAM)
What is GAME_ROM, and how do we get it?
NES Hardware
CPU
PPU
RAM
VRAM
Cartridge Slot
NES Hardware
CPU
PPU
RAM
VRAM
Cartridge Slot
Cartridges/Games
ROM files
/*
iNES File Structure, simplified a bit..
Offset (B) Size (B) Content
+---------------------------------------+
| 0 | 16 | Header |
+---------------------------------------+
| 16 | 16384 * X | PRG ROM data |
+---------------------------------------+
| Z | 8192 * Y | CHR ROM data |
+---------------------------------------+
*/
iNES Files
/*
iNES File Structure, simplified a bit..
Offset (B) Size (B) Content
+---------------------------------------+
| 0 | 16 | Header |
+---------------------------------------+
| 16 | 16384 * X | PRG ROM data |
+---------------------------------------+
| Z | 8192 * Y | CHR ROM data |
+---------------------------------------+
*/
iNES Files
/*
iNES File Structure, simplified a bit..
Offset (B) Size (B) Content
+---------------------------------------+
| 0 | 16 | Header |
+---------------------------------------+
| 16 | 16384 * X | PRG ROM data |
+---------------------------------------+
| Z | 8192 * Y | CHR ROM data |
+---------------------------------------+
*/
iNES Files
/*
iNES File Structure, simplified a bit..
Offset (B) Size (B) Content
+---------------------------------------+
| 0 | 16 | Header |
+---------------------------------------+
| 16 | 16384 * X | PRG ROM data |
+---------------------------------------+
| Z | 8192 * Y | CHR ROM data |
+---------------------------------------+
*/
iNES Files
/*
iNES File Structure, simplified a bit..
Offset (B) Size (B) Content
+---------------------------------------+
| 0 | 16 | Header |
+---------------------------------------+
| 16 | 16384 * X | PRG ROM data |
+---------------------------------------+
| Z | 8192 * Y | CHR ROM data |
+---------------------------------------+
*/
iNES Files
/*
iNES File Structure, simplified a bit..
Offset (B) Size (B) Content
+---------------------------------------+
| 0 | 16 | Header |
+---------------------------------------+
| 16 | 16384 * X | PRG ROM data |
+---------------------------------------+
| Z | 8192 * Y | CHR ROM data |
+---------------------------------------+
*/
iNES Files
X = number of PRG ROM "banks"
Y = number of CHR ROM "banks"
Z = 16 + (16384 * X)
/*
iNES Header Structure (NOTE: iNES version 1!)
-> Header, 16 bytes
Offset (B) Meaning
+-------------------------------------------------+
| 0-3 | iNES file identification string |
+-------------------------------------------------+
| 4 | PRG-ROM page count |
+-------------------------------------------------+
| 5 | CHR-ROM page count |
+-------------------------------------------------+
| 6 | Flags 6: |
| Mapper, mirroring, battery, trainer |
+-------------------------------------------------+
| 7 | Flags 7: |
| Mapper, VS/Playchoice, iNES 2.0? |
+-------------------------------------------------+
| 8 | Flags 8: |
| PRG-RAM size |
+-------------------------------------------------+
| 9 | Flags 9: |
| TV system |
+-------------------------------------------------+
| 10-15 | Unused padding (for iNES v1) |
+-------------------------------------------------+
*/
iNES Files
/*
iNES Header Structure (NOTE: iNES version 1!)
-> Header, 16 bytes
Offset (B) Meaning
+-------------------------------------------------+
| 0-3 | iNES file identification string |
+-------------------------------------------------+
| 4 | PRG-ROM page count |
+-------------------------------------------------+
| 5 | CHR-ROM page count |
+-------------------------------------------------+
| 6 | Flags 6: |
| Mapper, mirroring, battery, trainer |
+-------------------------------------------------+
| 7 | Flags 7: |
| Mapper, VS/Playchoice, iNES 2.0? |
+-------------------------------------------------+
| 8 | Flags 8: |
| PRG-RAM size |
+-------------------------------------------------+
| 9 | Flags 9: |
| TV system |
+-------------------------------------------------+
| 10-15 | Unused padding (for iNES v1) |
+-------------------------------------------------+
*/
iNES Files
"NES\x1A"
/*
iNES Header Structure (NOTE: iNES version 1!)
-> Header, 16 bytes
Offset (B) Meaning
+-------------------------------------------------+
| 0-3 | iNES file identification string |
+-------------------------------------------------+
| 4 | PRG-ROM page count |
+-------------------------------------------------+
| 5 | CHR-ROM page count |
+-------------------------------------------------+
| 6 | Flags 6: |
| Mapper, mirroring, battery, trainer |
+-------------------------------------------------+
| 7 | Flags 7: |
| Mapper, VS/Playchoice, iNES 2.0? |
+-------------------------------------------------+
| 8 | Flags 8: |
| PRG-RAM size |
+-------------------------------------------------+
| 9 | Flags 9: |
| TV system |
+-------------------------------------------------+
| 10-15 | Unused padding (for iNES v1) |
+-------------------------------------------------+
*/
iNES Files
X (count of PRG ROM pages after header)
/*
iNES Header Structure (NOTE: iNES version 1!)
-> Header, 16 bytes
Offset (B) Meaning
+-------------------------------------------------+
| 0-3 | iNES file identification string |
+-------------------------------------------------+
| 4 | PRG-ROM page count |
+-------------------------------------------------+
| 5 | CHR-ROM page count |
+-------------------------------------------------+
| 6 | Flags 6: |
| Mapper, mirroring, battery, trainer |
+-------------------------------------------------+
| 7 | Flags 7: |
| Mapper, VS/Playchoice, iNES 2.0? |
+-------------------------------------------------+
| 8 | Flags 8: |
| PRG-RAM size |
+-------------------------------------------------+
| 9 | Flags 9: |
| TV system |
+-------------------------------------------------+
| 10-15 | Unused padding (for iNES v1) |
+-------------------------------------------------+
*/
iNES Files
Y (count of CHR ROM pages after header and PRG ROM pages)
/*
iNES Header Structure (NOTE: iNES version 1!)
-> Header, 16 bytes
Offset (B) Meaning
+-------------------------------------------------+
| 0-3 | iNES file identification string |
+-------------------------------------------------+
| 4 | PRG-ROM page count |
+-------------------------------------------------+
| 5 | CHR-ROM page count |
+-------------------------------------------------+
| 6 | Flags 6: |
| Mapper, mirroring, battery, trainer |
+-------------------------------------------------+
| 7 | Flags 7: |
| Mapper, VS/Playchoice, iNES 2.0? |
+-------------------------------------------------+
| 8 | Flags 8: |
| PRG-RAM size |
+-------------------------------------------------+
| 9 | Flags 9: |
| TV system |
+-------------------------------------------------+
| 10-15 | Unused padding (for iNES v1) |
+-------------------------------------------------+
*/
iNES Files
Flags specific for this game, we will ignore most of these for now.
/*
iNES Header Structure (NOTE: iNES version 1!)
-> Header, 16 bytes
Offset (B) Meaning
+-------------------------------------------------+
| 0-3 | iNES file identification string |
+-------------------------------------------------+
| 4 | PRG-ROM page count |
+-------------------------------------------------+
| 5 | CHR-ROM page count |
+-------------------------------------------------+
| 6 | Flags 6: |
| Mapper, mirroring, battery, trainer |
+-------------------------------------------------+
| 7 | Flags 7: |
| Mapper, VS/Playchoice, iNES 2.0? |
+-------------------------------------------------+
| 8 | Flags 8: |
| PRG-RAM size |
+-------------------------------------------------+
| 9 | Flags 9: |
| TV system |
+-------------------------------------------------+
| 10-15 | Unused padding (for iNES v1) |
+-------------------------------------------------+
*/
iNES Files
Finally, the rest of the header is unused padding, at least for iNES version 1.
struct ines_rom_t
{
uint8_t prg_page_count;
uint8_t chr_page_count;
uint8_t** prg_pages;
uint8_t** chr_pages;
};
RESULT load_rom_file(const char* filepath, ines_rom_t& rom);
RESULT load_rom_mem(const uint8_t* data, long int size, ines_rom_t& rom);
Code: ROM reading
branch: slide-rom-loading
NES Hardware
CPU
PPU
RAM
VRAM
Cartridge Slot
NES CPU
Ricoh 2A03
NES CPU
Ricoh 2A03
- Based on MOS Technology 6502
- Runs at 1.79 MHz (1.66 MHz in PAL systems)
- 16-bit for memory addressing
16 Bit
Program Counter
PC
8 Bit
Stack Pointer
SP
8 Bit
Accumulator
A
8 Bit
Index Register X
X
8 Bit
Index Register Y
Y
N
Processor Status
P
V
B
D
I
Z
C
CPU Registers
N
Processor Status (P)
V
B
D
I
Z
C
CPU Registers
Negative
Overflow
"B" flag, ignore for now
Decimal (unused in the NES)
Interrupt Disable
Zero
Carry
N
Processor Status (P)
V
B
D
I
Z
C
CPU Registers
Negative
Overflow
"B" flag, ignore for now
Decimal (unused in the NES)
Interrupt Disable
Zero
Carry
struct cpu_t
{
// Registers
struct regs_t
{
union {
struct {
uint8_t C : 1;
uint8_t Z : 1;
uint8_t I : 1;
uint8_t D : 1;
uint8_t B : 2;
uint8_t O : 1;
uint8_t N : 1;
};
uint8_t P;
};
uint8_t A, X, Y;
uint16_t PC;
uint8_t S;
} regs;
};
Code: CPU Struct
NES Memory
RAM and BUS
NES Memory
RAM and BUS
- 2 KB of RAM
- ... but 16 bit address space
- enough for 64KB RAM??
NES Memory
RAM and BUS
PRG ROM
Save RAM
Expansion ROM
IO Registers
CPU RAM
0xFFFF
0x8000
0x6000
0x4020
0x2000
0x0000
NES Memory
RAM and BUS
PRG ROM
Save RAM
Expansion ROM
IO Registers
CPU RAM
0xFFFF
0x8000
0x6000
0x4020
0x2000
0x0000
"Zero Page" RAM
Stack
RAM
0x0000
0x0100
0x0200
0x0800
NES Memory
RAM and BUS
PRG ROM
Save RAM
Expansion ROM
IO Registers
CPU RAM
0xFFFF
0x8000
0x6000
0x4020
0x2000
0x0000
PRG ROM from our iNES ROM
NES Memory
RAM and BUS
PRG ROM
Save RAM
Expansion ROM
IO Registers
CPU RAM
0xFFFF
0x8000
0x6000
0x4020
0x2000
0x0000
PRG ROM Upper
PRG ROM Lower
struct cpu_t
{
// Registers
struct regs_t
{
// ...
} regs;
// RAM and Stack
uint8_t ram[0x700];
uint8_t stack[0x100];
// Currently mapped prg rom banks
uint8_t* prgrom_lower;
uint8_t* prgrom_upper;
};
Code: CPU Struct
cont.
Code: CPU
Structures, init and initial run loop
branch: slide-cpu1
show;
- passing along iNES rom to "CPU"/emulator
- clear "RAM" etc
- implement memory read/write functions
- show initial cpu step loop
6502 Assembly
6502 Assembly
Code: CPU opcodes
4e45 531a 0101 0000 0000 0000 0000 0000
2070 0600 2064 0600 2078 0600 20b7 0400
20bc 0100 0108 0208 0200 0501 0002 0101
0105 0105 0102 0102 db60 e255 1420 01f9
...
Donkey Kong iNES ROM file
Code: CPU opcodes
4e45 531a 0101 0000 0000 0000 0000 0000
2070 0600 2064 0600 2078 0600 20b7 0400
20bc 0100 0108 0208 0200 0501 0002 0101
0105 0105 0102 0102 db60 e255 1420 01f9
...
Donkey Kong iNES ROM file
Header (16 bytes)
Code: CPU opcodes
4e45 531a 0101 0000 0000 0000 0000 0000
2070 0600 2064 0600 2078 0600 20b7 0400
20bc 0100 0108 0208 0200 0501 0002 0101
0105 0105 0102 0102 db60 e255 1420 01f9
...
Donkey Kong iNES ROM file
PRG BANK
Code: CPU opcodes
4e45 531a 0101 0000 0000 0000 0000 0000
2070 0600 2064 0600 2078 0600 20b7 0400
20bc 0100 0108 0208 0200 0501 0002 0101
0105 0105 0102 0102 db60 e255 1420 01f9
...
Donkey Kong iNES ROM file
PRG BANK
16 Bit
Program Counter
PC
Code: CPU opcodes
4e45 531a 0101 0000 0000 0000 0000 0000
2070 0600 2064 0600 2078 0600 20b7 0400
20bc 0100 0108 0208 0200 0501 0002 0101
0105 0105 0102 0102 db60 e255 1420 01f9
...
Donkey Kong iNES ROM file
PRG BANK
Code: CPU opcodes
Interpret:
Into (and executing):
JSR $0670
0x20 0x70 0x06
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
opcode
operands
instruction (from PRG ROM)
6502 assembly
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
instruction (from PRG ROM)
6502 assembly
Source: https://www.masswerk.at/6502/6502_instruction_set.html
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
instruction (from PRG ROM)
6502 assembly
Source: https://www.masswerk.at/6502/6502_instruction_set.html
What is "abs"?
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Addressing modes
-
immediate
-
zeropage
-
zeropage, x-indexed
-
zeropage, y-indexed
-
indirect, x-indexed
-
indirect, y-indexed
-
absolute
-
absolute, x-indexed
-
absolute, y-indexed
Tells us what data, or memory, the instruction will read/write from/to.
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Addressing modes
-
immediate
-
zeropage
-
zeropage, x-indexed
-
zeropage, y-indexed
-
indirect, x-indexed
-
indirect, y-indexed
-
absolute
-
absolute, x-indexed
-
absolute, y-indexed
This gives a complete address with the next 2 bytes
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Addressing modes
-
immediate
-
zeropage
-
zeropage, x-indexed
-
zeropage, y-indexed
-
indirect, x-indexed
-
indirect, y-indexed
-
absolute
-
absolute, x-indexed
-
absolute, y-indexed
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Addressing modes
-
immediate
-
zeropage
-
zeropage, x-indexed
-
zeropage, y-indexed
-
indirect, x-indexed
-
indirect, y-indexed
-
absolute
-
absolute, x-indexed
-
absolute, y-indexed
Use the address right after the opcode (PC+1)
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Addressing modes
-
immediate
-
zeropage
-
zeropage, x-indexed
-
zeropage, y-indexed
-
indirect, x-indexed
-
indirect, y-indexed
-
absolute
-
absolute, x-indexed
-
absolute, y-indexed
Use the address of the value at PC+1. (8 bit force it to be on zeropage, 0x00nn)
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Addressing modes
-
immediate
-
zeropage
-
zeropage, x-indexed
-
zeropage, y-indexed
-
indirect, x-indexed
-
indirect, y-indexed
-
absolute
-
absolute, x-indexed
-
absolute, y-indexed
Same as zeropage, but offset with register X
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Addressing modes
-
immediate
-
zeropage
-
zeropage, x-indexed
-
zeropage, y-indexed
-
indirect, x-indexed
-
indirect, y-indexed
-
absolute
-
absolute, x-indexed
-
absolute, y-indexed
Same as zeropage, but offset with register Y
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Addressing modes
-
immediate
-
zeropage
-
zeropage, x-indexed
-
zeropage, y-indexed
-
indirect, x-indexed
-
indirect, y-indexed
-
absolute
-
absolute, x-indexed
-
absolute, y-indexed
Read the value of the immediate byte. Use this value + X (low nibble), and this value + X + 1 (high nibble) as an address
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Addressing modes
-
immediate
-
zeropage
-
zeropage, x-indexed
-
zeropage, y-indexed
-
indirect, x-indexed
-
indirect, y-indexed
-
absolute
-
absolute, x-indexed
-
absolute, y-indexed
Read the value of the immediate byte. Use this value (low nibble), and this value + 1 (high nibble) as an address. Add Y to this address
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Addressing modes
-
immediate
-
zeropage
-
zeropage, x-indexed
-
zeropage, y-indexed
-
indirect, x-indexed
-
indirect, y-indexed
-
absolute
-
absolute, x-indexed
-
absolute, y-indexed
Same as absolute, but offset with register X
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Addressing modes
-
immediate
-
zeropage
-
zeropage, x-indexed
-
zeropage, y-indexed
-
indirect, x-indexed
-
indirect, y-indexed
-
absolute
-
absolute, x-indexed
-
absolute, y-indexed
Same as absolute, but offset with register Y
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Implementing Opcodes
What we know:
- 0x20 is the JSR opcode
- The instruction uses the addressing mode: "absolute", so we need to also fetch the successive 2 bytes from PC and PC+1
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Implementing Opcodes
What we DON'T know:
- How JSR changes the CPU/emulator state?
Thankfully there is an insane amount of information on this, for example:
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Implementing Opcodes
JSR JSR Jump to new location saving return address JSR
Operation: PC + 2 toS, (PC + 1) -> PCL N Z C I D V
(PC + 2) -> PCH _ _ _ _ _ _
(Ref: 8.1)
+----------------+-----------------------+---------+---------+----------+
| Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles|
+----------------+-----------------------+---------+---------+----------+
| Absolute | JSR Oper | 20 | 3 | 6 |
+----------------+-----------------------+---------+---------+----------+
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Implementing Opcodes
JSR JSR Jump to new location saving return address JSR
Operation: PC + 2 toS, (PC + 1) -> PCL N Z C I D V
(PC + 2) -> PCH _ _ _ _ _ _
(Ref: 8.1)
+----------------+-----------------------+---------+---------+----------+
| Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles|
+----------------+-----------------------+---------+---------+----------+
| Absolute | JSR Oper | 20 | 3 | 6 |
+----------------+-----------------------+---------+---------+----------+
- Push PC+2 to stack
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Implementing Opcodes
JSR JSR Jump to new location saving return address JSR
Operation: PC + 2 toS, (PC + 1) -> PCL N Z C I D V
(PC + 2) -> PCH _ _ _ _ _ _
(Ref: 8.1)
+----------------+-----------------------+---------+---------+----------+
| Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles|
+----------------+-----------------------+---------+---------+----------+
| Absolute | JSR Oper | 20 | 3 | 6 |
+----------------+-----------------------+---------+---------+----------+
- Push PC+2 to stack
- Read PC+1 as new PC (low nibble)
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Implementing Opcodes
JSR JSR Jump to new location saving return address JSR
Operation: PC + 2 toS, (PC + 1) -> PCL N Z C I D V
(PC + 2) -> PCH _ _ _ _ _ _
(Ref: 8.1)
+----------------+-----------------------+---------+---------+----------+
| Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles|
+----------------+-----------------------+---------+---------+----------+
| Absolute | JSR Oper | 20 | 3 | 6 |
+----------------+-----------------------+---------+---------+----------+
- Push PC+2 to stack
- Read PC+1 as new PC (low nibble)
- Read PC+2 as new PC (high nibble)
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Implementing Opcodes
JSR JSR Jump to new location saving return address JSR
Operation: PC + 2 toS, (PC + 1) -> PCL N Z C I D V
(PC + 2) -> PCH _ _ _ _ _ _
(Ref: 8.1)
+----------------+-----------------------+---------+---------+----------+
| Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles|
+----------------+-----------------------+---------+---------+----------+
| Absolute | JSR Oper | 20 | 3 | 6 |
+----------------+-----------------------+---------+---------+----------+
- Push PC+2 to stack
- Read PC+1 as new PC (low nibble)
- Read PC+2 as new PC (high nibble)
Note; No changes to status register (P)!
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Implementing Opcodes
JSR JSR Jump to new location saving return address JSR
Operation: PC + 2 toS, (PC + 1) -> PCL N Z C I D V
(PC + 2) -> PCH _ _ _ _ _ _
(Ref: 8.1)
+----------------+-----------------------+---------+---------+----------+
| Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles|
+----------------+-----------------------+---------+---------+----------+
| Absolute | JSR Oper | 20 | 3 | 6 |
+----------------+-----------------------+---------+---------+----------+
Bonus 1;
It tells us how many bytes the instruction is.
Code: CPU opcodes
JSR $0670
0x20 0x70 0x06
Implementing Opcodes
JSR JSR Jump to new location saving return address JSR
Operation: PC + 2 toS, (PC + 1) -> PCL N Z C I D V
(PC + 2) -> PCH _ _ _ _ _ _
(Ref: 8.1)
+----------------+-----------------------+---------+---------+----------+
| Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles|
+----------------+-----------------------+---------+---------+----------+
| Absolute | JSR Oper | 20 | 3 | 6 |
+----------------+-----------------------+---------+---------+----------+
Bonus 2;
It tells us how many CPU cycles the instruction takes to complete.
Code: CPU opcodes
Implementing Opcodes
show code
show simple switch case for address mode and opcode
branch: slide-cpu1
Code: CPU opcodes
Implementing Opcodes
Insert "Draw an owl" meme here,
implement ALL opcodes...
Code: Memory
show code to hide memory access
stack operations
address mode switch
etc
show code... gulp
- LUT of OPS
- full switch of addressing modes
- updated step loop
- new expanded memory read/write functions
branch: slide-cpu2
"nestest"
How to verify our CPU behaves as it should
show code
- show nestest.rom and nestest.log
- show new logging hooks
with this we can come a long way in verifying our CPU and operations implementaiton
branch: slide-nestest
NES Hardware
CPU (and APU)
PPU
RAM
VRAM
Cartridge Slot
NES Hardware
Pt 2
NES PPU
Nametables and sprites
PPU Memory/VRAM
Pattern table 0
0x1000
0x0000
0x2000
Nametable 0
Pattern table 1
Nametable 1
Nametable 2
Nametable 3
0x2400
0x2800
0x2C00
0x3000
Mirrors nametables
0x3F00
Palette RAM indexes
0x3F20
Mirrors palette
0x4000
Patterns, nametables and palette
Pattern table 0
0x1000
0x0000
Object Attribute
Memory
(Separate 256 bytes)
OAM RAM
PPU Memory/VRAM
Pattern table 0
0x1000
0x0000
0x2000
Nametable 0
Pattern table 1
Nametable 1
Nametable 2
Nametable 3
0x2400
0x2800
0x2C00
0x3000
Mirrors nametables
0x3F00
Palette RAM indexes
0x3F20
Mirrors palette
0x4000
Patterns, nametables and palette
Pattern table 0
0x1000
0x0000
Object Attribute
Memory
(Separate 256 bytes)
OAM RAM
Backgrounds
Sprites
Tiles, the CHR data from our iNES ROM
NES Hardware
CPU (and APU)
PPU
RAM
VRAM
Cartridge Slot
https://wiki.nesdev.org/w/index.php/PPU_registers
PPU Registers
CPU (and APU)
PPU
RAM
VRAM
Cartridge Slot
PPUCTRL
PPUMASK
PPUSTATUS
OAMADDR
OAMDATA
PPUSCROLL
PPUADDR
PPUDATA
OAMDMA
PPU Registers
PPUCTRL
PPUMASK
PPUSTATUS
OAMADDR
OAMDATA
PPUSCROLL
PPUADDR
PPUDATA
OAMDMA
PPU control register;
- selects current/base nametable
- VRAM address increment, +1 or +32 (horisontal or vertical update)
- sprite pattern table address
- background pattern table address
PPU Registers
PPUCTRL
PPUMASK
PPUSTATUS
OAMADDR
OAMDATA
PPUSCROLL
PPUADDR
PPUDATA
OAMDMA
PPU mask / render control;
- grayscale toggling
- show/hide BG leftmost 8 pixels on screen
- show/hide sprite leftmost 8 pixels on screen
- show/hide background
- show/hide sprites
- color emphasising
PPU Registers
PPUCTRL
PPUMASK
PPUSTATUS
OAMADDR
OAMDATA
PPUSCROLL
PPUADDR
PPUDATA
OAMDMA
PPU status;
- sprite overflow flag
- sprite 0 hit test result
- "vertical blank started" flag
PPU Registers
PPUCTRL
PPUMASK
PPUSTATUS
OAMADDR
OAMDATA
PPUSCROLL
PPUADDR
PPUDATA
OAMDMA
OAM address;
Set the address that will be used when updating the OAM (sprite memory).
PPU Registers
PPUCTRL
PPUMASK
PPUSTATUS
OAMADDR
OAMDATA
PPUSCROLL
PPUADDR
PPUDATA
OAMDMA
OAM data;
Write to this address to write data to the OAM (sprite memory). Data will be written the address where OAMADDR was set.
After each write, OAMADDR will increment, which means that games can set OAMADDR once and continuously write to OAMDATA with new sprite information.
PPU Registers
PPUCTRL
PPUMASK
PPUSTATUS
OAMADDR
OAMDATA
PPUSCROLL
PPUADDR
PPUDATA
OAMDMA
PPU scroll position;
Writing to this registers determines the vertical/horizontal scroll of the game.
(We will mostly ignore this, Donkey Kong doesn't use scrolling.)
PPU Registers
PPUCTRL
PPUMASK
PPUSTATUS
OAMADDR
OAMDATA
PPUSCROLL
PPUADDR
PPUDATA
OAMDMA
PPU VRAM address;
Similar to OAMADDR, games write to this register to indicate where in VRAM the CPU wants to write.
PPU Registers
PPUCTRL
PPUMASK
PPUSTATUS
OAMADDR
OAMDATA
PPUSCROLL
PPUADDR
PPUDATA
OAMDMA
PPU VRAM data;
Also similar to OAMDATA, games continuously write to this register to fill VRAM with background data/tiles.
After a write to this register, PPUADDR will increment with either 1 or 32, determined by the value of PPUCTRL (ie write tiles horizontally or vertically).
PPU Registers
PPUCTRL
PPUMASK
PPUSTATUS
OAMADDR
OAMDATA
PPUSCROLL
PPUADDR
PPUDATA
OAMDMA
OAMDMA - "OAM direct memory access";
Used to update 256 bytes of OAM (sprite memory) in one call, instead of continuously writing to OAMDATA.
The value written to this register determines from which CPU RAM address the 256 will be taken from; 0xYY -> 0xYY00 - 0xYYFF.
The DMA will write the data to VRAM starting at OAMADDR.
PPU Registers
PPUCTRL
PPUMASK
PPUSTATUS
OAMADDR
OAMDATA
PPUSCROLL
PPUADDR
PPUDATA
OAMDMA
0x2000
0x2001
0x2002
0x2003
0x2004
0x2005
0x2006
0x2007
0x4014
CPU visible address
PPU register name
PPU Struct
struct ppu_t
{
// most important registers to get up and running
uint8_t ppuctrl;
uint8_t ppumask;
uint8_t ppustatus;
uint8_t oamaddr;
uint16_t ppuaddr;
// VRAM
uint8_t vram[0x800]; // 2kb vram
uint8_t oam[64*4];
// Mapped CHR ROM
uint8_t* chr_rom;
};
struct emu_t
{
cpu_t cpu;
ppu_t ppu;
};
show code
- show struct
- show simple PPU step loop
- show ppu call from emu step
- show PPU register and memory access
nestest now passes, with render x+y
branch: slide-ppu1
Rendering Nametables and Sprites
We know what the "current" nametable is (see PPUCTRL register).
We know which pattern table the BG and sprites use.
-> We can just loop over all the tiles in the nametable and OAM and draw each tile.
Let's first make sure we can render a tile!
Code time...
branch: slide-ppu2
The end??
Questions?
Extra: Colors?
Extra: NES Misc.
APU, joypads etc
Writing a NES emulator
By Sven Andersson
Writing a NES emulator
- 9,659