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??

todo;

 

show image from donkey kong, identify background and sprites

show as different "layers" (images or gifs)

show nametable grid

+ attribute grid

 

describe the game loop (execute game logic, update background and sprites, render result to screen)

 

define naming conventions; CHR, tiles and sprites

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

Palettes

First row;

  Byte 1: 5

  Byte 2: 7

Palettes

First row;

  Byte 1: 5

  Byte 2: 7

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.

Code: ROM reading

branch: slide-rom-loading

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);

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:

https://www.nesdev.com/6502.txt

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     |
  +----------------+-----------------------+---------+---------+----------+
  1. 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     |
  +----------------+-----------------------+---------+---------+----------+
  1. Push PC+2 to stack
  2. 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     |
  +----------------+-----------------------+---------+---------+----------+
  1. Push PC+2 to stack
  2. Read PC+1 as new PC (low nibble)
  3. 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     |
  +----------------+-----------------------+---------+---------+----------+
  1. Push PC+2 to stack
  2. Read PC+1 as new PC (low nibble)
  3. 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

Text

NES Misc.

APU, joypads etc

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...

Code: Using nestest

Subtitle

todo;

Code: A "fake" PPU

Subtitle

todo;

Code: VRAM

Looking at our nametables and OAM

todo;

Extra: Colors?

Subtitle

Writing a NES emulator - BACKUP

By Sven Andersson

Writing a NES emulator - BACKUP

  • 2,911