Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion lib/cartridge/detect_cartridge.ml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ let f ~rom_bytes =
| MBC1
| MBC1_RAM
| MBC1_RAM_BATTERY -> (module Mbc1 : Cartridge_intf.S)
| _ -> assert false
| MBC2
| MBC2_BATTERY -> (module Mbc2 : Cartridge_intf.S)
| other ->
failwith (Printf.sprintf "Unsupported cartridge type: %s" (Cartridge_type.show other))
84 changes: 84 additions & 0 deletions lib/cartridge/mbc2.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
(* MBC2 - Memory Bank Controller 2

Features:
- Up to 256KB ROM (16 banks)
- Built-in 512×4 bits RAM (only lower nibble used)
- RAM at 0xA000-0xA1FF (mirrored in 0xA200-0xBFFF)

Register writes:
- 0x0000-0x3FFF with bit 8 = 0: RAM enable (0x0A enables)
- 0x0000-0x3FFF with bit 8 = 1: ROM bank number (4 bits, 0→1)

Reference: https://gbdev.io/pandocs/MBC2.html *)

open Uints

type t = {
rom_bytes : Bigstringaf.t;
ram_bytes : Bigstringaf.t;
rom_bank_count : int;
mutable ram_enabled : bool;
mutable rom_bank_num : int;
}

let create ~rom_bytes =
let h = Cartridge_header.create ~rom_bytes in
let rom_bank_count = Cartridge_header.get_rom_bank_count h in
(* MBC2 has built-in 512×4 bit RAM *)
let ram_bytes = Bigstringaf.create 512 in
{
rom_bytes;
ram_bytes;
rom_bank_count;
ram_enabled = false;
rom_bank_num = 1;
}

let read_byte t addr =
let addr = Uint16.to_int addr in
match addr with
| _ when addr <= 0x3FFF ->
Bigstringaf.unsafe_get t.rom_bytes addr
|> Uint8.of_char
| _ when addr <= 0x7FFF ->
let offset = 0x4000 * t.rom_bank_num + (addr - 0x4000) in
Bigstringaf.unsafe_get t.rom_bytes offset
|> Uint8.of_char
| _ when 0xA000 <= addr && addr <= 0xBFFF ->
(* RAM - 512 bytes mirrored, only lower 4 bits valid
This memory is at A000–A1FF and mirrored at
A200–BFFF hence the 0x1FF below. *)
if t.ram_enabled then
let ram_addr = (addr - 0xA000) land 0x1FF in
let value = Bigstringaf.unsafe_get t.ram_bytes ram_addr |> Char.code in
Uint8.of_int (value lor 0xF0) (* Upper 4 bits read as 1 *)
else
Uint8.of_int 0xFF
| _ -> assert false

let write_byte t ~addr ~data =
let addr = Uint16.to_int addr in
let data = Uint8.to_int data in
match addr with
| _ when addr <= 0x3FFF ->
if addr land 0x100 = 0 then
t.ram_enabled <- (data land 0x0F) = 0x0A
else begin
let bank = data land 0x0F in
let bank = if bank = 0 then 1 else bank in
t.rom_bank_num <- bank mod t.rom_bank_count
end
| _ when 0x4000 <= addr && addr <= 0x7FFF ->
(* Writes to this area do nothing on MBC2 *)
()
| _ when 0xA000 <= addr && addr <= 0xBFFF ->
(* RAM - only lower 4 bits written *)
if t.ram_enabled then begin
let ram_addr = (addr - 0xA000) land 0x1FF in
Bigstringaf.unsafe_set t.ram_bytes ram_addr (Char.unsafe_chr (data land 0x0F))
end
| _ -> assert false

let accepts _ addr =
let addr = Uint16.to_int addr in
(addr <= 0x7FFF) || (0xA000 <= addr && addr <= 0xBFFF)
1 change: 1 addition & 0 deletions lib/cartridge/mbc2.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include Cartridge_intf.S
Binary file added resource/test_roms/mooneye/mbc2/bits_ramg.gb
Binary file not shown.
Binary file added resource/test_roms/mooneye/mbc2/bits_romb.gb
Binary file not shown.
Binary file added resource/test_roms/mooneye/mbc2/bits_unused.gb
Binary file not shown.
Binary file added resource/test_roms/mooneye/mbc2/ram.gb
Binary file not shown.
Binary file added resource/test_roms/mooneye/mbc2/rom_1Mb.gb
Binary file not shown.
Binary file added resource/test_roms/mooneye/mbc2/rom_2Mb.gb
Binary file not shown.
Binary file added resource/test_roms/mooneye/mbc2/rom_512kb.gb
Binary file not shown.
41 changes: 19 additions & 22 deletions test/rom_test/mooneye/mooneye_utils.ml
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,25 @@ module Make (Cartridge : Cartridge_intf.S) = struct
let run_result = Camlboy.run_instruction camlboy in
let prev_instr = Camlboy.For_tests.prev_inst camlboy in
match prev_instr, run_result with
| JR (None, i8), Frame_ended famebuffer ->
if Int8.to_int i8 <> -3 then
loop ()
else begin
Printf.printf "%s\n" @@ Camlboy.show camlboy;
famebuffer
|> Array.iteri (fun i row ->
if row |> Array.for_all (fun color -> color = `White) then
()
else begin
let show_color = function
| `Black -> '#'
| `Dark_gray -> '@'
| `Light_gray -> 'x'
| `White -> '-'
in
Printf.printf "%03d:" i;
row |> Array.iter (fun color -> show_color color |> print_char);
print_newline ()
end
)
end
| JR (None, i8), Frame_ended famebuffer
when List.mem (Int8.to_int i8) [-2; -3] ->
Printf.printf "%s\n" @@ Camlboy.show camlboy;
famebuffer
|> Array.iteri (fun i row ->
if row |> Array.for_all (fun color -> color = `White) then
()
else begin
let show_color = function
| `Black -> '#'
| `Dark_gray -> '@'
| `Light_gray -> 'x'
| `White -> '-'
in
Printf.printf "%03d:" i;
row |> Array.iter (fun color -> show_color color |> print_char);
print_newline ()
end
)
| _, _ -> loop ()
in
loop ()
Expand Down
86 changes: 86 additions & 0 deletions test/rom_test/mooneye/test_mooneye_mbc2.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
open Camlboy_lib
module M = Mooneye_utils.Make(Mbc2)

let%expect_test "bits_ramg.gb" =
M.run_test_rom_and_print_framebuffer "mbc2/bits_ramg.gb";

[%expect{|
A:$00 F:ZN-C BC:$0305 DE:$080D HL:$1522 SP:$E000 PC:$4989
008:-#######-----------------------------------###---#---#----------------------------------------------------------------------------------------------------------
009:----#-----####-----###-----#--------------#---#--#--#-----------------------------------------------------------------------------------------------------------
010:----#----#----#---#-------####------------#---#--#-#------------------------------------------------------------------------------------------------------------
011:----#----######----##------#--------------#---#--###------------------------------------------------------------------------------------------------------------
012:----#----#-----------#-----#--------------#---#--#--#-----------------------------------------------------------------------------------------------------------
013:----#-----####----###------##--------------###---#---#---------------------------------------------------------------------------------------------------------- |}]

let%expect_test "bits_romb.gb" =
M.run_test_rom_and_print_framebuffer "mbc2/bits_romb.gb";

[%expect{|
A:$00 F:ZN-C BC:$0305 DE:$080D HL:$1522 SP:$E000 PC:$4879
008:-#######-----------------------------------###---#---#----------------------------------------------------------------------------------------------------------
009:----#-----####-----###-----#--------------#---#--#--#-----------------------------------------------------------------------------------------------------------
010:----#----#----#---#-------####------------#---#--#-#------------------------------------------------------------------------------------------------------------
011:----#----######----##------#--------------#---#--###------------------------------------------------------------------------------------------------------------
012:----#----#-----------#-----#--------------#---#--#--#-----------------------------------------------------------------------------------------------------------
013:----#-----####----###------##--------------###---#---#---------------------------------------------------------------------------------------------------------- |}]

let%expect_test "bits_unused.gb" =
M.run_test_rom_and_print_framebuffer "mbc2/bits_unused.gb";

[%expect{|
A:$00 F:ZN-C BC:$0305 DE:$080D HL:$1522 SP:$E000 PC:$4089
008:-#######-----------------------------------###---#---#----------------------------------------------------------------------------------------------------------
009:----#-----####-----###-----#--------------#---#--#--#-----------------------------------------------------------------------------------------------------------
010:----#----#----#---#-------####------------#---#--#-#------------------------------------------------------------------------------------------------------------
011:----#----######----##------#--------------#---#--###------------------------------------------------------------------------------------------------------------
012:----#----#-----------#-----#--------------#---#--#--#-----------------------------------------------------------------------------------------------------------
013:----#-----####----###------##--------------###---#---#---------------------------------------------------------------------------------------------------------- |}]

let%expect_test "ram.gb" =
M.run_test_rom_and_print_framebuffer "mbc2/ram.gb";

[%expect{|
A:$00 F:ZN-C BC:$0305 DE:$080D HL:$1522 SP:$E000 PC:$4879
008:-#######-----------------------------------###---#---#----------------------------------------------------------------------------------------------------------
009:----#-----####-----###-----#--------------#---#--#--#-----------------------------------------------------------------------------------------------------------
010:----#----#----#---#-------####------------#---#--#-#------------------------------------------------------------------------------------------------------------
011:----#----######----##------#--------------#---#--###------------------------------------------------------------------------------------------------------------
012:----#----#-----------#-----#--------------#---#--#--#-----------------------------------------------------------------------------------------------------------
013:----#-----####----###------##--------------###---#---#---------------------------------------------------------------------------------------------------------- |}]

let%expect_test "rom_512kb.gb" =
M.run_test_rom_and_print_framebuffer "mbc2/rom_512kb.gb";

[%expect{|
A:$00 F:ZN-C BC:$0305 DE:$080D HL:$1522 SP:$E000 PC:$487A
008:-#######-----------------------------------###---#---#----------------------------------------------------------------------------------------------------------
009:----#-----####-----###-----#--------------#---#--#--#-----------------------------------------------------------------------------------------------------------
010:----#----#----#---#-------####------------#---#--#-#------------------------------------------------------------------------------------------------------------
011:----#----######----##------#--------------#---#--###------------------------------------------------------------------------------------------------------------
012:----#----#-----------#-----#--------------#---#--#--#-----------------------------------------------------------------------------------------------------------
013:----#-----####----###------##--------------###---#---#---------------------------------------------------------------------------------------------------------- |}]

let%expect_test "rom_1Mb.gb" =
M.run_test_rom_and_print_framebuffer "mbc2/rom_1Mb.gb";

[%expect{|
A:$00 F:ZN-C BC:$0305 DE:$080D HL:$1522 SP:$E000 PC:$487A
008:-#######-----------------------------------###---#---#----------------------------------------------------------------------------------------------------------
009:----#-----####-----###-----#--------------#---#--#--#-----------------------------------------------------------------------------------------------------------
010:----#----#----#---#-------####------------#---#--#-#------------------------------------------------------------------------------------------------------------
011:----#----######----##------#--------------#---#--###------------------------------------------------------------------------------------------------------------
012:----#----#-----------#-----#--------------#---#--#--#-----------------------------------------------------------------------------------------------------------
013:----#-----####----###------##--------------###---#---#---------------------------------------------------------------------------------------------------------- |}]

let%expect_test "rom_2Mb.gb" =
M.run_test_rom_and_print_framebuffer "mbc2/rom_2Mb.gb";

[%expect{|
A:$00 F:ZN-C BC:$0305 DE:$080D HL:$1522 SP:$E000 PC:$487A
008:-#######-----------------------------------###---#---#----------------------------------------------------------------------------------------------------------
009:----#-----####-----###-----#--------------#---#--#--#-----------------------------------------------------------------------------------------------------------
010:----#----#----#---#-------####------------#---#--#-#------------------------------------------------------------------------------------------------------------
011:----#----######----##------#--------------#---#--###------------------------------------------------------------------------------------------------------------
012:----#----#-----------#-----#--------------#---#--#--#-----------------------------------------------------------------------------------------------------------
013:----#-----####----###------##--------------###---#---#---------------------------------------------------------------------------------------------------------- |}]