oboy/src/core/memory.ml

110 lines
3.1 KiB
OCaml
Raw Normal View History

2016-02-02 21:25:53 +01:00
(**
* 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.
*)
2015-03-22 16:57:03 +01:00
open Bytes
2015-03-23 22:38:11 +01:00
open Printf
2015-03-22 16:57:03 +01:00
2016-01-23 13:16:18 +01:00
(** @see http://bgb.bircd.org/pandocs.htm#memorymap
@see http://imrannazar.com/GameBoy-Emulation-in-JavaScript:-Memory *)
(** Common addresses *)
2019-05-08 12:11:17 +02:00
let gDIV = 0xFF04 (* divider register *)
let gTIMA = 0xFF05 (* timer counter *)
let gTMA = 0xFF06 (* timer modulo *)
let gTAC = 0xFF07 (* timer control *)
2015-03-22 13:56:32 +01:00
type map = {
rom_bank_00 : bytes; (* cartridge, 16KB *)
rom_bank_01 : bytes; (* additional bank, 16KB *)
2016-01-23 13:16:18 +01:00
vram : bytes; (* Video RAM, 8KB *)
2015-05-08 14:47:30 +02:00
wram_bank_0 : bytes; (* work RAM, 4KB *)
wram_bank_1 : bytes; (* work RAM, 4KB *)
2016-01-23 13:16:18 +01:00
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;
2015-03-22 13:56:32 +01:00
}
let init (cartridge: Cartridge.t) =
2016-01-23 13:16:18 +01:00
let map = {
2015-03-22 13:56:32 +01:00
rom_bank_00 = sub cartridge.full_rom 0 0x4000;
rom_bank_01 = create 0x4000;
vram = create 0x2000;
2015-05-08 14:47:30 +02:00
wram_bank_0 = create 0x1000;
wram_bank_1 = create 0x1000;
2015-03-22 16:57:03 +01:00
io = create 0x0080;
2015-03-23 12:50:02 +01:00
hram = create 0x2000;
interrupt = create 1
2016-01-23 13:16:18 +01:00
} 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; }
2015-03-22 16:57:03 +01:00
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)
2015-05-08 14:47:30 +02:00
| 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)
2015-03-22 16:57:03 +01:00
| x when x < 0xFF00 -> failwith "Unimplemented memory range."
| x when x < 0xFF80 -> mem.io, x - 0xFF00
2015-03-23 12:50:02 +01:00
| x when x < 0xFFFF -> mem.hram, x - 0xFF80
| 0xFFFF -> mem.interrupt, 0
2015-03-24 11:27:25 +01:00
| x -> eprintf "Memory access 0x%06X\n" x;
2015-03-23 22:38:11 +01:00
failwith "Invalid memory range."
2015-03-22 16:57:03 +01:00
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
2016-01-23 13:16:18 +01:00
(** 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
2016-03-01 00:22:56 +01:00
if should_inc_tima then begin
2016-01-23 13:16:18 +01:00
let overflow = inc mem.map gTIMA in
2016-03-01 00:22:56 +01:00
if overflow then begin
2016-01-23 13:16:18 +01:00
let tma = get mem.map gTMA in
set mem.map gTIMA tma
(* TODO: INT 50 - Timer interupt *)
2016-03-01 00:22:56 +01:00
end
end