Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Making A Gameboy Emulator in Ruby

Making A Gameboy Emulator in Ruby

Released in 1989 the Gameboy was the first handheld console of the Gameboy series to be released by Nintendo. It featured games such as Pokemon Red & Blue, Tetris, Super Mario Land and went on to sell 118 million units worldwide.

My talk discusses what components make up a Gameboy, such as the CPU, RAM, Graphics and Game Cartridge. How each component works as an individual unit and works as part of a higher machine. Then how to imitate the behaviour of the Gameboy in ruby to make an emulator.

Colby Swandale

February 09, 2017
Tweet

More Decks by Colby Swandale

Other Decks in Technology

Transcript

  1. Making a
    Gameboy
    Emulator in Ruby

    View Slide

  2. Colby Swandale
    0xColby

    View Slide

  3. • Developed by Nintendo Japan
    • Released April 1989
    • Sold 118 million units (including GBC)
    • 1049 Games including Tetris, Super Mario Land,
    Pokemon Red & Blue
    • 15 Hours Battery Life
    Nintendo Gameboy

    View Slide

  4. What is an
    emulator?

    View Slide

  5. In computing, an emulator is
    hardware or software that
    enables one computer system
    (called the host) to behave like
    another computer system (called
    the guest). An emulator typically
    enables the host system to run
    software or use peripheral
    devices designed for the guest
    system.
    https://en.wikipedia.org/wiki/Emulator

    View Slide

  6. View Slide

  7. CPU PPU Memory Cartridge

    View Slide

  8. CPU

    View Slide

  9. • Sharp LR35902
    • 4.19Mhz clockspeed
    • 8 bit operations
    • 16 bit memory bus
    • Similar to the Zilog z80 & Intel 8080 processor
    CPU

    View Slide

  10. Registers

    View Slide

  11. A F
    B C
    D E
    H L
    SP
    PC
    CPU: Registers
    • Stores data
    • Very quick relative to higher memory
    • Physically sits in the CPU
    • General and special purposes
    • A - L: 1 byte
    • SP, PC: 2 bytes

    View Slide

  12. class CPU
    end
    CPU

    View Slide

  13. class CPU
    def initialize
    @a, @b, @c, @d, @e, @h, @l, @f = 0x00
    @pc, @sp = 0x0000
    end
    end
    CPU

    View Slide

  14. Instructions

    View Slide

  15. LD A, B ADD A,B SUB D AND B
    XOR B OR H RLA DEC BC
    PUSH HL CALL 0x2BC6 NOP LD D,0x15
    LD 0x15,A POP BC EI HALT
    CPU: Instructions

    View Slide

  16. Opcode
    (hex)
    0 1 2 3
    0 LD A, B ADD A,B SUB D AND B
    1 XOR B OR H RLA DEC BC
    2 PUSH HL CALL 0x2BC6 NOP LD D,0x15
    3 LD 0x15,A POP BC EI HALT
    CPU: Opcode Table

    View Slide

  17. Opcode
    (hex)
    0 1 2 3
    0 LD A, B ADD A,B SUB D AND B
    1 XOR B OR H RLA DEC BC
    2 PUSH HL CALL 0x2BC6 NOP LD D,0x15
    3 LD 0x15,A POP BC EI HALT
    CPU: Opcode Table

    View Slide

  18. CPU: Opcode Table
    EI (0x32) 0011 0010

    View Slide

  19. class CPU
    OPCODE = [
    :nop, :ld_bc_d16, :ld_bc_a, :inc_bc,
    :inc_b, :dec_b, :ld_b_d8, :rlca, :ld_a16_sp,
    :add_hl_bc, :ld_a_bc, :dec_bc, :inc_c, :dec_c,
    :ld_c_d8, :rrca, :stop_0, :ld_de_d16, :ld_de_a,
    :inc_de, :inc_d, :dec_d, :ld_d_d8, :rla, :jr_r8,
    :add_hl_de, :ld_a_de, :dec_de, :inc_e, :dec_e,
    :ld_e_d8, :rra,
    ...
    end
    CPU: Opcode Table

    View Slide

  20. class CPU
    def ld_b_c
    @b = @c
    end
    end
    CPU: LD B,C

    View Slide

  21. class CPU
    def inc_b
    result = @b + 1
    @b = result & 0xFF
    end
    end
    CPU: INC B

    View Slide

  22. Fetch and Execute

    View Slide

  23. Fetch Next
    Instruction
    Read Byte
    Program
    Counter
    CPU: Fetch And Execute
    Interpret
    Instruction
    Execute
    Instruction
    Memory

    View Slide

  24. class CPU
    def tick
    operation = $mmu[@pc]
    @pc += 1
    self.public_send OPCODE[operation]
    end
    end
    CPU: Tick

    View Slide

  25. Instruction Timing

    View Slide

  26. Instruction Cycles
    NOP 4
    LD A,A 4
    CALL (a16) 16
    AND (d8) 8
    INC D 4
    CPU: Timing

    View Slide

  27. class CPU
    def tick
    @cycles = OPCODE_TIMING[operation]
    end
    end
    CPU: Timing
    operation = $mmu[@pc]
    @pc += 1
    self.public_send OPCODE[operation]

    View Slide

  28. Memory

    View Slide

  29. • Controlled by the Memory
    Management Unit
    • 64 KB Storage
    • 65,535 (0xFFFF) address space
    • Spread Across multiple chips

    View Slide

  30. Game Program Video General IO
    MMU: Memory Map
    0x0 0xFFFF

    View Slide

  31. class MMU
    MEMORY_SIZE = 65_536 # addresses
    def initialise
    @memory = Array.new MEMORY_SIZE, 0
    end
    end
    Memory Management Unit

    View Slide

  32. class MMU
    def [](i)
    case i
    when 0x0000...0x8000 # ROM Bank 0 + n
    # read from cartridge
    when 0x8000...0xA000 # Video RAM
    @memory[i]
    when 0xA000...0xC000 # RAM Bank
    # read from cartridge
    when 0xC000..0xFFFF # User RAM, Sprites, IO, Stack
    @memory[i]
    end
    end
    end
    MMU

    View Slide

  33. class MMU
    def []=(i, v)
    case i
    when 0x0000...0x8000 # ROM Bank 0 + n
    # write to cartridge
    when 0x8000...0xA000 # Video RAM
    @memory[i] = v
    when 0xA000...0xC000 # RAM Bank
    # write to cartridge
    when 0xC000..0xFFFF # User RAM, Sprites, IO, Stack
    @memory[i] = v
    end
    end
    end
    MMU

    View Slide

  34. $mmu = MMU.new
    MMU

    View Slide

  35. Memory
    Registers

    View Slide

  36. Game Program Video General IO &
    Hardware

    View Slide

  37. Memory Registers
    Register Address
    LCDC 0xFF40
    TIMA 0xFF50
    SCY 0xFF42
    SCX 0xFF43
    IE 0xFFFF

    View Slide

  38. class PPU
    FRAMEBUFFER_SIZE = 23_040 # 160 x 144 (screen size)
    def initialize
    @framebuffer = Array.new FRAMEBUFFER_SIZE, 0
    @mode = :vertical_blank
    @modeclock = 0
    end
    end
    PPU

    View Slide

  39. Memory
    Program
    Hardware
    Component
    Memory Registers

    View Slide

  40. Memory
    Program
    Memory Registers

    Write Byte
    NR10
    Read Byte

    View Slide

  41. Picture
    Processing Unit

    View Slide

  42. • 160 x 144 pixels (renders 255x255)
    • 4 color pallete
    • Emulates CRT
    • 60hz refresh rate
    • 8Kb VRAM
    Picture Processing Unit

    View Slide

  43. Game Program Video General
    IO &
    OAM
    PPU: Memory
    Picture
    Processing
    Unit
    Screen

    View Slide

  44. PPU: Background, Windows and Sprites
    Background
    World, occupies entire screen
    Windows
    Menus, Fixed positions on screen
    Sprites
    Characters, items, objects moving
    indepently of the background

    View Slide

  45. PPU: Modes
    1 Frame
    80 172 204 4560
    OAM
    Read
    VRAM
    Read
    HBlank VBlank
    1 Line
    NO
    VRAM
    NO
    VRAM VRAM
    VRAM

    View Slide

  46. class PPU
    def tick(cycles)
    @modeclock += cycles
    case @mode
    when :horizontal_blank
    hblank if @modeclock >= 80
    when :vertical_blank
    vblank if @modeclock >= 172
    when :sprite_read
    oam if @modeclock >= 204
    when :vram_read
    vram if @modeclock >= 4560
    end
    end
    end
    PPU: Modes

    View Slide

  47. Viewport

    View Slide

  48. PPU: Viewport

    View Slide

  49. Tile System

    View Slide

  50. PPU: Tiles

    View Slide

  51. PPU: Tile
    8x8 pixels
    20x16 tiles on screen
    32x32 tiles rendered by PPU
    16 Bytes
    2 bits per pixel encoding
    4 Colors
    White, Light Grey, Dark Grey, Black

    View Slide

  52. PPU: Tile
    8x8 pixels
    20x16 tiles on screen
    32x32 rendered by PPU
    16 Bytes
    2 bits per pixel encoding
    4 Colors
    White, Light Grey, Dark Grey, Black
    0 0 1 1 1 1 1 1 1 0 1 0 1 0 0 0
    1 1 0 0 0 0 1 1 1 0 1 0 1 0 1 0
    1 1 0 0 1 1 1 0 1 0 1 0 1 0 1 0
    1 1 0 0 1 1 0 1 0 1 1 0 1 0 1 0
    1 1 0 0 1 1 0 0 0 0 0 1 0 1 0 1
    1 1 0 0 1 1 0 0 0 0 0 0 0 0 0 0
    1 1 0 0 1 0 1 1 0 0 0 0 0 0 0 0
    1 1 0 0 1 0 1 1 0 0 0 0 0 0 0 0

    View Slide

  53. PPU: Tiles

    View Slide

  54. Map

    View Slide

  55. • Holds pointer to tile in
    memory
    • 32x32 tiles
    • Used for Backgrounds and
    Windows
    PPU: Map

    View Slide

  56. 0 0 0 0 0 0 0 0 0 0
    0 0 0 0 0 0 0 0 0 0
    0 0 2 2 0 0 2 2 0 0
    0 0 2 2 0 0 2 2 0 0
    0 0 2 2 2 2 2 2 0 0
    0 0 2 2 0 0 2 2 0 0
    0 0 2 2 0 0 2 2 0 0
    0 0 0 0 0 0 0 0 0 0
    0 0 3 3 3 3 3 3 0 0
    0 0 0 0 0 0 0 0 0 0
    0 2 3
    PPU: Map

    View Slide

  57. PPU: Map

    View Slide

  58. OAM

    View Slide

  59. Game Program Video General
    IO &
    OAM
    PPU: OAM

    View Slide

  60. PPU: OAM
    Attributes
    Position X
    Position Y
    Tile Number
    Priority
    Flip X
    Flip Y
    Pallete

    View Slide

  61. Cartridge

    View Slide

  62. • 29 different cartridge types
    • Up to 2Mb ROM
    • Up to 32Kb of external memory
    • supports external hardware i.e: RTC, RAM
    • Controlled by the Memory Bank Controller
    Cartridge

    View Slide

  63. Game Program Video General IO
    Cartridge: Memory

    View Slide

  64. Available RAM
    Game Program
    Cartridge: Memory

    View Slide

  65. 16KB 16KB 16KB 16KB 16KB 16KB 16KB 16KB
    16KB 16KB 16KB 16KB 16KB 16KB 16KB 16KB
    16KB 16KB 16KB 16KB 16KB 16KB 16KB 16KB
    16KB 16KB 16KB 16KB 16KB 16KB 16KB 16KB
    16KB 16KB 16KB 16KB 16KB 16KB 16KB 16KB

    View Slide

  66. 0 1 2 3 4 5 6 7
    8 9 10 11 12 13 14 15
    16 17 18 19 20 21 22 23
    24 25 26 27 28 29 30 31
    32 33 34 35 36 37 .. 255

    View Slide

  67. Game Program Video General IO
    Cartridge: Memory

    View Slide

  68. Bank 0 Bank n
    0x0 0x4000 0x8000
    Cartridge: Memory

    View Slide

  69. 0x0000 … 0x4000 0x4001 … 0x8000
    0 1 2 3
    4 5 6 7
    8 9 10 11
    12 13 14 15
    16 17 18 19
    20 21 22 23
    MMU
    Cartridge
    Controller
    Cartridge: Banking
    Read
    Byte
    Program
    Write Byte

    View Slide

  70. 0 1 2 3
    4 5 6 7
    8 9 10 11
    12 13 14 15
    16 17 18 19
    20 21 22 23
    MMU
    Cartridge
    Controller
    0x0000 … 0x4000
    0x4001 … 0x8000
    Write
    Byte
    Cartridge: Banking
    Program
    Write Byte
    Change
    Bank

    View Slide

  71. require 'forwardable'
    class Cartridge
    extend Forwardable
    CARTRIDGE_TYPE_MEM_LOC = 0x147
    def_delegators :@mbc, :[], :[]=
    def initialize(program)
    cartridge_type = program[CARTRIDGE_TYPE_MEM_LOC]
    @mbc = cartrdige_controller(cartridge_type, program)
    end
    def cartrdige_controller type, rom
    controller_const(type).new rom
    end
    end

    View Slide

  72. class Cartridge
    def controller_const(controller_byte)
    case controller_byte
    when 0x00, 0x8, 0x9
    MBC::ROM
    when 0x1, 0x2, 0x3
    MBC::MBC1
    when 0x5, 0x6
    MBC::MBC2
    when 0xF, 0x10, 0x11, 0x12, 0x13
    MBC::MBC3
    when 0x15, 0x16, 0x17
    MBC::MBC4
    when 0x19, 0x1B, 0x1C, 0x1D, 0x1E
    MBC::MBC5
    end
    end
    end

    View Slide

  73. class MBC::MBC1
    EXTERNAL_RAM_SIZE = 0x2000
    def initialize(program)
    @rom_bank = 1
    @ram_bank = 1
    @game_program = program
    @ram = Array.new EXTERNAL_RAM_SIZE, 0
    end
    end
    Cartridge

    View Slide

  74. class MBC::MBC1
    def [](i)
    case i
    when 0x0...0x4000 # ROM Bank 0
    @game_program[i]
    when 0x4000...0x8000 # ROM Bank n
    addr = i - 0x4000
    offset = @rom_bank * 0x4000
    @game_program[offset + addr]
    end
    end
    end
    Cartridge

    View Slide

  75. class MBC::MBC1
    def []=(i,v)
    case i
    when 0x2000...0x4000
    @rom_bank = v
    when 0x4000...0x6000
    @ram_bank = v
    when 0xA000...0xC000
    offset = @ram_bank * 0x8000
    @ram[offset + addr] = v
    end
    end
    end
    Cartridge

    View Slide

  76. Updating The
    MMU

    View Slide

  77. class MMU
    MEMORY_SIZE = 65_536 # bytes
    def initialise(game_program)
    @memory = Array.new MEMORY_SIZE, 0
    @cartridge = Cartridge.new game_program
    end
    end
    Updating MMU

    View Slide

  78. class MMU
    def [](i)
    case i
    when 0x0000...0x8000 # ROM Bank 0 + n
    @cartridge[i]
    when 0x8000...0xA000 # Video RAM
    @memory[i]
    when 0xA000...0xC000 # RAM Bank
    @cartridge[i]
    when 0xC000..0xFFFF # RAM, Sprites, IO, Stack
    @memory[i]
    end
    end
    end
    Updating MMU

    View Slide

  79. Bringing Everything
    Together

    View Slide

  80. class Emulator
    end
    Emulator

    View Slide

  81. class Emulator
    def initialize(rom_path)
    game = File.binread(rom_path).bytes
    @cpu = CPU.new
    @ppu = PPU.new
    @screen = Screen.new
    $mmu = MMU.new(game)
    end
    end
    Emulator

    View Slide

  82. class Emulator
    def run
    loop do
    @cpu.tick
    @ppu.tick @cpu.cycles
    @screen.render @ppu.framebuffer if @ppu.can_render?
    end
    end
    end
    def initialize(rom_path)
    game = File.binread(rom_path).bytes
    @cpu = CPU.new
    @ppu = PPU.new
    @screen = Screen.new
    $mmu = MMU.new(game)
    end

    View Slide

  83. Emulator.new('/path/to/rom').run
    Emulator

    View Slide

  84. What I Didn’t
    Talk About

    View Slide

  85. View Slide

  86. colby-swandale/waterfoul

    View Slide

  87. Thank You!

    View Slide

  88. Sources
    Gameboy Opcode Table: http://www.pastraiser.com/cpu/gameboy/
    gameboy_opcodes.html
    Gameboy CPU Manual: http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf
    Gameboy Pandocs: http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf

    View Slide