(** * Copyright (c) 2015, Fabien Freling * All rights reserved. * * This source code is licensed under the BSD 2-clause license found in the * LICENSE file at the top level directory of this source tree. *) open Bytes open Printf (** @see http://bgb.bircd.org/pandocs.htm#memorymap @see http://imrannazar.com/GameBoy-Emulation-in-JavaScript:-Memory *) (** Common addresses *) let gDIV = 0xFF04 (* divider register *) let gTIMA = 0xFF05 (* timer counter *) let gTMA = 0xFF06 (* timer modulo *) let gTAC = 0xFF07 (* timer control *) type map = { rom_bank_00 : bytes; (* cartridge, 16KB *) rom_bank_01 : bytes; (* additional bank, 16KB *) vram : bytes; (* Video RAM, 8KB *) wram_bank_0 : bytes; (* work RAM, 4KB *) wram_bank_1 : bytes; (* work RAM, 4KB *) io : bytes; (* I/O ports *) hram : bytes; (* High RAM, 8KB *) interrupt : bytes; (* Interrupt Enable Register *) } type t = { map : map; timer_div : Timer.t; tima : Timer.t; } let init (cartridge: Cartridge.t) = let map = { rom_bank_00 = sub cartridge.full_rom 0 0x4000; rom_bank_01 = create 0x4000; vram = create 0x2000; wram_bank_0 = create 0x1000; wram_bank_1 = create 0x1000; io = create 0x0080; hram = create 0x2000; interrupt = create 1 } in (** Init register values @see http://bgb.bircd.org/pandocs.htm#powerupsequence *) let zero = char_of_int 0 in set map.io 0x05 zero; (* TIMA, 0xFF05 *) set map.io 0x06 zero; (* TMA, 0xFF06 *) set map.io 0x07 zero; (* TAC, 0xFF07 *) let timer_div = Timer.create 16384 true in let tima = Timer.create_tima 0 in { map; timer_div; tima; } let get_mem_bank mem addr = match addr with | x when x < 0x4000 -> mem.rom_bank_00, x | x when x < 0x8000 -> mem.rom_bank_01, (x - 0x4000) | x when x < 0xA000 -> mem.vram, (x - 0x8000) | x when x < 0xC000 -> failwith "Unimplemented memory range." | x when x < 0xD000 -> mem.wram_bank_0, (x - 0xC000) | x when x < 0xE000 -> mem.wram_bank_1, (x - 0xD000) | x when x < 0xFF00 -> failwith "Unimplemented memory range." | x when x < 0xFF80 -> mem.io, x - 0xFF00 | x when x < 0xFFFF -> mem.hram, x - 0xFF80 | 0xFFFF -> mem.interrupt, 0 | x -> eprintf "Memory access 0x%06X\n" x; failwith "Invalid memory range." let get mem addr = let m, x = get_mem_bank mem addr in get m x let set mem addr c = let m, x = get_mem_bank mem addr in set m x c (** Increment byte in memory, wrap value in case of overflow *) let inc mem addr = let m, x = get_mem_bank mem addr in let value = Bytes.get m x |> int_of_char in let inc_value = value + 1 in let overflow = inc_value > 0xFF in let c = inc_value mod 0x01FF |> char_of_int in Bytes.set m x c; overflow let update_timers mem cycles = let should_inc_div = Timer.update mem.timer_div cycles in if should_inc_div then ignore (inc mem.map gDIV); let should_inc_tima = Timer.update mem.tima cycles in if should_inc_tima then begin let overflow = inc mem.map gTIMA in if overflow then begin let tma = get mem.map gTMA in set mem.map gTIMA tma (* TODO: INT 50 - Timer interupt *) end end