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.

81faf651bd1a239298307c7fd6efe944?s=128

Colby Swandale

February 09, 2017
Tweet

Transcript

  1. Making a Gameboy Emulator in Ruby

  2. Colby Swandale 0xColby

  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
  4. What is an emulator?

  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
  6. None
  7. CPU PPU Memory Cartridge

  8. CPU

  9. • Sharp LR35902 • 4.19Mhz clockspeed • 8 bit operations

    • 16 bit memory bus • Similar to the Zilog z80 & Intel 8080 processor CPU
  10. Registers

  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
  12. class CPU end CPU

  13. class CPU def initialize @a, @b, @c, @d, @e, @h,

    @l, @f = 0x00 @pc, @sp = 0x0000 end end CPU
  14. Instructions

  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
  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
  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
  18. CPU: Opcode Table EI (0x32) 0011 0010

  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
  20. class CPU def ld_b_c @b = @c end end CPU:

    LD B,C
  21. class CPU def inc_b result = @b + 1 @b

    = result & 0xFF end end CPU: INC B
  22. Fetch and Execute

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

    Execute Interpret Instruction Execute Instruction Memory
  24. class CPU def tick operation = $mmu[@pc] @pc += 1

    self.public_send OPCODE[operation] end end CPU: Tick
  25. Instruction Timing

  26. Instruction Cycles NOP 4 LD A,A 4 CALL (a16) 16

    AND (d8) 8 INC D 4 CPU: Timing
  27. class CPU def tick @cycles = OPCODE_TIMING[operation] end end CPU:

    Timing operation = $mmu[@pc] @pc += 1 self.public_send OPCODE[operation]
  28. Memory

  29. • Controlled by the Memory Management Unit • 64 KB

    Storage • 65,535 (0xFFFF) address space • Spread Across multiple chips
  30. Game Program Video General IO MMU: Memory Map 0x0 0xFFFF

  31. class MMU MEMORY_SIZE = 65_536 # addresses def initialise @memory

    = Array.new MEMORY_SIZE, 0 end end Memory Management Unit
  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
  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
  34. $mmu = MMU.new MMU

  35. Memory Registers

  36. Game Program Video General IO & Hardware

  37. Memory Registers Register Address LCDC 0xFF40 TIMA 0xFF50 SCY 0xFF42

    SCX 0xFF43 IE 0xFFFF
  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
  39. Memory Program Hardware Component Memory Registers

  40. Memory Program Memory Registers Write Byte NR10 Read Byte

  41. Picture Processing Unit

  42. • 160 x 144 pixels (renders 255x255) • 4 color

    pallete • Emulates CRT • 60hz refresh rate • 8Kb VRAM Picture Processing Unit
  43. Game Program Video General IO & OAM PPU: Memory Picture

    Processing Unit Screen
  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
  45. PPU: Modes 1 Frame 80 172 204 4560 OAM Read

    VRAM Read HBlank VBlank 1 Line NO VRAM NO VRAM VRAM VRAM
  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
  47. Viewport

  48. PPU: Viewport

  49. Tile System

  50. PPU: Tiles

  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
  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
  53. PPU: Tiles

  54. Map

  55. • Holds pointer to tile in memory • 32x32 tiles

    • Used for Backgrounds and Windows PPU: Map
  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
  57. PPU: Map

  58. OAM

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

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

    Flip X Flip Y Pallete
  61. Cartridge

  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
  63. Game Program Video General IO Cartridge: Memory

  64. Available RAM Game Program Cartridge: Memory

  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
  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
  67. Game Program Video General IO Cartridge: Memory

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

  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
  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
  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
  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
  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
  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
  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
  76. Updating The MMU

  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
  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
  79. Bringing Everything Together

  80. class Emulator end Emulator

  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
  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
  83. Emulator.new('/path/to/rom').run Emulator

  84. What I Didn’t Talk About

  85. None
  86. colby-swandale/waterfoul

  87. Thank You!

  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