It's fun to do! ™
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... :)
GENie build system
https://github.com/bkaradzic/GENie
C++ish, ie sane non modern C++
https://gist.github.com/bkaradzic/2e39896bc7d8c34e042b
Third party libraries
Info, NES spec, 6502 datasheet etc;
https://wiki.nesdev.org/
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
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.)
First row;
Byte 1: 5
Byte 2: 7
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
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
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
First row;
Byte 1: 5
Byte 2: 7
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?
CPU
PPU
RAM
VRAM
Cartridge Slot
CPU
PPU
RAM
VRAM
Cartridge Slot
CHR data (sprites and background)
PRG data (game code/logic)
PRG and CHR data stored on cartridge
CPU
PPU
RAM
VRAM
Cartridge Slot
CHR data (sprites and background)
PRG data (game code/logic)
PRG and CHR data stored on cartridge
CPU
PPU
RAM
VRAM
Cartridge Slot
CHR data (sprites and background)
PRG data (game code/logic)
PRG and CHR data stored on cartridge
CPU
PPU
RAM
VRAM
Cartridge Slot
CPU (and APU)
PPU
RAM
VRAM
Cartridge Slot
CPU
CPU (and APU)
PPU
RAM
VRAM
Cartridge Slot
CPU Simulation
while true:
instr = get_next_instruction(GAME_ROM)
execute(instr)
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)
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)
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)
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?
CPU
PPU
RAM
VRAM
Cartridge Slot
CPU
PPU
RAM
VRAM
Cartridge Slot
/*
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 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 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 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 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 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 |
+---------------------------------------+
*/
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 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) |
+-------------------------------------------------+
*/
"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) |
+-------------------------------------------------+
*/
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) |
+-------------------------------------------------+
*/
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) |
+-------------------------------------------------+
*/
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) |
+-------------------------------------------------+
*/
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);
branch: slide-rom-loading
CPU
PPU
RAM
VRAM
Cartridge Slot
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
N
Processor Status (P)
V
B
D
I
Z
C
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
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;
};
PRG ROM
Save RAM
Expansion ROM
IO Registers
CPU RAM
0xFFFF
0x8000
0x6000
0x4020
0x2000
0x0000
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
PRG ROM
Save RAM
Expansion ROM
IO Registers
CPU RAM
0xFFFF
0x8000
0x6000
0x4020
0x2000
0x0000
PRG ROM from our iNES ROM
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;
};
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
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
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)
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
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
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
Interpret:
Into (and executing):
JSR $0670
0x20 0x70 0x06
JSR $0670
0x20 0x70 0x06
opcode
operands
instruction (from PRG ROM)
6502 assembly
JSR $0670
0x20 0x70 0x06
instruction (from PRG ROM)
6502 assembly
Source: https://www.masswerk.at/6502/6502_instruction_set.html
JSR $0670
0x20 0x70 0x06
instruction (from PRG ROM)
6502 assembly
Source: https://www.masswerk.at/6502/6502_instruction_set.html
What is "abs"?
JSR $0670
0x20 0x70 0x06
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.
JSR $0670
0x20 0x70 0x06
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
JSR $0670
0x20 0x70 0x06
immediate
zeropage
zeropage, x-indexed
zeropage, y-indexed
indirect, x-indexed
indirect, y-indexed
absolute
absolute, x-indexed
absolute, y-indexed
JSR $0670
0x20 0x70 0x06
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)
JSR $0670
0x20 0x70 0x06
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)
JSR $0670
0x20 0x70 0x06
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
JSR $0670
0x20 0x70 0x06
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
JSR $0670
0x20 0x70 0x06
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
JSR $0670
0x20 0x70 0x06
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
JSR $0670
0x20 0x70 0x06
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
JSR $0670
0x20 0x70 0x06
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
JSR $0670
0x20 0x70 0x06
What we know:
JSR $0670
0x20 0x70 0x06
What we DON'T know:
Thankfully there is an insane amount of information on this, for example:
JSR $0670
0x20 0x70 0x06
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 |
+----------------+-----------------------+---------+---------+----------+
JSR $0670
0x20 0x70 0x06
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 |
+----------------+-----------------------+---------+---------+----------+
JSR $0670
0x20 0x70 0x06
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 |
+----------------+-----------------------+---------+---------+----------+
JSR $0670
0x20 0x70 0x06
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 |
+----------------+-----------------------+---------+---------+----------+
JSR $0670
0x20 0x70 0x06
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 |
+----------------+-----------------------+---------+---------+----------+
Note; No changes to status register (P)!
JSR $0670
0x20 0x70 0x06
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.
JSR $0670
0x20 0x70 0x06
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.
show code
show simple switch case for address mode and opcode
branch: slide-cpu1
Insert "Draw an owl" meme here,
implement ALL opcodes...
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
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
CPU (and APU)
PPU
RAM
VRAM
Cartridge Slot
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
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
CPU (and APU)
PPU
RAM
VRAM
Cartridge Slot
https://wiki.nesdev.org/w/index.php/PPU_registers
CPU (and APU)
PPU
RAM
VRAM
Cartridge Slot
PPUCTRL
PPUMASK
PPUSTATUS
OAMADDR
OAMDATA
PPUSCROLL
PPUADDR
PPUDATA
OAMDMA
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
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
PPUCTRL
PPUMASK
PPUSTATUS
OAMADDR
OAMDATA
PPUSCROLL
PPUADDR
PPUDATA
OAMDMA
PPU status;
- sprite overflow flag
- sprite 0 hit test result
- "vertical blank started" flag
PPUCTRL
PPUMASK
PPUSTATUS
OAMADDR
OAMDATA
PPUSCROLL
PPUADDR
PPUDATA
OAMDMA
OAM address;
Set the address that will be used when updating the OAM (sprite memory).
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.
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.)
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.
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).
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.
PPUCTRL
PPUMASK
PPUSTATUS
OAMADDR
OAMDATA
PPUSCROLL
PPUADDR
PPUDATA
OAMDMA
0x2000
0x2001
0x2002
0x2003
0x2004
0x2005
0x2006
0x2007
0x4014
CPU visible address
PPU register name
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
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