Slide 1

Slide 1 text

Making a Gameboy Emulator in Ruby

Slide 2

Slide 2 text

Colby Swandale 0xColby

Slide 3

Slide 3 text

• 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

Slide 4

Slide 4 text

What is an emulator?

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

CPU PPU Memory Cartridge

Slide 8

Slide 8 text

CPU

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

Registers

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

class CPU end CPU

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

Instructions

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

CPU: Opcode Table EI (0x32) 0011 0010

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Fetch and Execute

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Instruction Timing

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Memory

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

$mmu = MMU.new MMU

Slide 35

Slide 35 text

Memory Registers

Slide 36

Slide 36 text

Game Program Video General IO & Hardware

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Memory Program Hardware Component Memory Registers

Slide 40

Slide 40 text

Memory Program Memory Registers Write Byte NR10 Read Byte

Slide 41

Slide 41 text

Picture Processing Unit

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Viewport

Slide 48

Slide 48 text

PPU: Viewport

Slide 49

Slide 49 text

Tile System

Slide 50

Slide 50 text

PPU: Tiles

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

PPU: Tiles

Slide 54

Slide 54 text

Map

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

PPU: Map

Slide 58

Slide 58 text

OAM

Slide 59

Slide 59 text

Game Program Video General IO & OAM PPU: OAM

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

Cartridge

Slide 62

Slide 62 text

• 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

Slide 63

Slide 63 text

Game Program Video General IO Cartridge: Memory

Slide 64

Slide 64 text

Available RAM Game Program Cartridge: Memory

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

Game Program Video General IO Cartridge: Memory

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

Updating The MMU

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

Bringing Everything Together

Slide 80

Slide 80 text

class Emulator end Emulator

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

What I Didn’t Talk About

Slide 85

Slide 85 text

No content

Slide 86

Slide 86 text

colby-swandale/waterfoul

Slide 87

Slide 87 text

Thank You!

Slide 88

Slide 88 text

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