diff --git a/fpt/src/lib.rs b/fpt/src/lib.rs index c087ab2..ff7038d 100644 --- a/fpt/src/lib.rs +++ b/fpt/src/lib.rs @@ -1,6 +1,7 @@ #![feature(bigint_helper_methods)] #![feature(array_chunks)] #![feature(iter_intersperse)] +#![feature(extend_one)] use std::collections::VecDeque; @@ -46,7 +47,7 @@ impl Gameboy { } /// Sets CPU and hardware registers to the values found in the DMG0 column in the tables at - /// https://gbdev.io/pandocs/Power_Up_Sequence.html#console-state-after-boot-rom-hand-off + /// pub fn boot_fake(&mut self) { // CPU registers self.cpu.set_af(0x0100); diff --git a/fpt/src/lr35902.rs b/fpt/src/lr35902.rs index aafdc4f..624b9ae 100644 --- a/fpt/src/lr35902.rs +++ b/fpt/src/lr35902.rs @@ -277,6 +277,7 @@ impl LR35902 { self.bus.write(index as usize, value); // TODO: watchpoint trigger write // Write triggers (TODO: better solution) + // TODO: fds do this inside the memmory if index == memory::map::BANK as u16 && value != 0 { self.bus.unload_bootrom(); } diff --git a/fpt/src/memory.rs b/fpt/src/memory.rs deleted file mode 100644 index da57001..0000000 --- a/fpt/src/memory.rs +++ /dev/null @@ -1,448 +0,0 @@ -use std::cell::{Ref, RefCell, RefMut}; -use std::ops::Range; -use std::rc::Rc; - -use crate::bw; - -pub type Address = usize; -pub type MemoryRange = Range
; - -/// You can access these consts like this: -/// ``` -/// assert_eq!(fpt::memory::map::ROM_DATA.start, 0x0100); -/// ``` -pub mod map { - use super::{Address, MemoryRange}; - - //------------------------------------------------------------------------- - // Memory map - //------------------------------------------------------------------------- - - /// This is where the bootrom lives - pub const BOOTROM: MemoryRange = 0x0000..0x0100; - - /// The Cartridge Header - pub const ROM_DATA: MemoryRange = 0x0100..0x0150; - - /// User Program Area (32 KB) - /// 0x0000..0x4000 From cartridge, usually a fixed bank - /// 0x4000..0x8000 From cartridge, switchable bank via mapper (if any) pub const USER_PROGRAM: MemoryRange = 0x0000..0x8000; - - /// Video RAM (8 KB) - In CGB mode, switchable bank 0/1 - pub const VRAM: MemoryRange = 0x8000..0xA000; - - /// External Expansion Working RAM (8 KB) - From cartridge, switchable bank if any - pub const EXT_WRAM: MemoryRange = 0xA000..0xC000; - - /// Unit Working RAM (8 KB) - pub const WRAM: MemoryRange = 0xC000..0xE000; - - /// Not usable (Mirror of C000~DDFF (ECHO RAM)) https://gbdev.io/pandocs/Memory_Map.html#echo-ram - pub const NOT_USABLE1: MemoryRange = 0xE000..0xFE00; - - /// Object Attribute Memory (40 OBJs, 40 x 32 bits) - pub const OAM: MemoryRange = 0xFE00..0xFEA0; - - /// Not usable https://gbdev.io/pandocs/Memory_Map.html#fea0-feff-range - pub const NOT_USABLE2: MemoryRange = 0xFEA0..0xFF00; - - //------------------------------------------------------------------------- - // I/O Registers - //------------------------------------------------------------------------- - - /// Joypad - pub const JOYP: Address = 0xFF00; - /// Serial transfer data - pub const SB: Address = 0xFF01; - /// Serial transfer control - pub const SC: Address = 0xFF02; - /// Divider register - pub const DIV: Address = 0xFF04; - /// Timer counter - pub const TIMA: Address = 0xFF05; - /// Timer modulo - pub const TMA: Address = 0xFF06; - /// Timer control - pub const TAC: Address = 0xFF07; - - //------------------------------------------------------------------------- - // I/O: Sound - //------------------------------------------------------------------------- - - /// Sound channel 1 sweep - pub const NR10: Address = 0xFF10; - /// Sound channel 1 length timer & duty cycle - pub const NR11: Address = 0xFF11; - /// Sound channel 1 volume & envelope - pub const NR12: Address = 0xFF12; - /// Sound channel 1 period low - pub const NR13: Address = 0xFF13; - /// Sound channel 1 period high & control - pub const NR14: Address = 0xFF14; - /// Sound channel 2 length timer & duty cycle - pub const NR21: Address = 0xFF16; - /// Sound channel 2 volume & envelope - pub const NR22: Address = 0xFF17; - /// Sound channel 2 period low - pub const NR23: Address = 0xFF18; - /// Sound channel 2 period high & control - pub const NR24: Address = 0xFF19; - /// Sound channel 3 DAC enable - pub const NR30: Address = 0xFF1A; - /// Sound channel 3 length timer - pub const NR31: Address = 0xFF1B; - /// Sound channel 3 output level - pub const NR32: Address = 0xFF1C; - /// Sound channel 3 period low - pub const NR33: Address = 0xFF1D; - /// Sound channel 3 period high & control - pub const NR34: Address = 0xFF1E; - /// Sound channel 4 length timer - pub const NR41: Address = 0xFF20; - /// Sound channel 4 volume & envelope - pub const NR42: Address = 0xFF21; - /// Sound channel 4 frequency & randomness - pub const NR43: Address = 0xFF22; - /// Sound channel 4 control - pub const NR44: Address = 0xFF23; - /// Master volume & VIN panning - pub const NR50: Address = 0xFF24; - /// Sound panning - pub const NR51: Address = 0xFF25; - /// Sound on/off - pub const NR52: Address = 0xFF26; - /// Wave RAM - pub const WAVE_RAM: MemoryRange = 0xFF30..0xFF40; - - //------------------------------------------------------------------------- - // IO: PPU - //------------------------------------------------------------------------- - - /// LCD control - pub const LCDC: Address = 0xFF40; - /// LCD status - pub const STAT: Address = 0xFF41; - /// Viewport Y position - pub const SCY: Address = 0xFF42; - /// Viewport X position - pub const SCX: Address = 0xFF43; - /// LCD Y coordinate - pub const LY: Address = 0xFF44; - /// LY compare - pub const LYC: Address = 0xFF45; - /// OAM DMA source address & start - pub const DMA: Address = 0xFF46; - /// BG palette data (DMG) - pub const BGP: Address = 0xFF47; - /// OBJ palette 0 data (DMG) - pub const OBP0: Address = 0xFF48; - /// OBJ palette 1 data (DMG) - pub const OBP1: Address = 0xFF49; - /// Window Y position - pub const WY: Address = 0xFF4A; - /// Window X position plus 7 - pub const WX: Address = 0xFF4B; - - /// BANK register: Set to non-zero to disable boot ROM - pub const BANK: Address = 0xFF50; - - //------------------------------------------------------------------------- - // CGB extra - // https://gbdev.io/pandocs/CGB_Registers.html - //------------------------------------------------------------------------- - - /// Prepare speed switch (CGB) - pub const KEY1: Address = 0xFF4C; - /// VRAM bank (CGB) - pub const VBK: Address = 0xFF4F; - /// VRAM DMA source high (CGB) - pub const HDMA1: Address = 0xFF51; - /// VRAM DMA source low (CGB) - pub const HDMA2: Address = 0xFF52; - /// VRAM DMA destination high (CGB) - pub const HDMA3: Address = 0xFF53; - /// VRAM DMA destination low (CGB) - pub const HDMA4: Address = 0xFF54; - /// VRAM DMA length/mode/start (CGB) - pub const HDMA5: Address = 0xFF55; - /// Infrared communications port (GGB) - pub const RP: Address = 0xFF56; - /// Background color palette specification / Background palette index (CGB) - pub const BCPS: Address = 0xFF68; - /// Background color palette data / Background palette data (CGB) - pub const BCPD: Address = 0xFF69; - /// OBJ color palette specification / OBJ palette index (CGB) - pub const OCPS: Address = 0xFF6A; - /// OBJ color palette data / OBJ palette data (CGB) - pub const OCPD: Address = 0xFF6B; - /// Object priority mode (CGB) - pub const OPRI: Address = 0xFF6C; - /// WRAM bank (CGB) pub const SVBK: Address = 0xFF70; - /// Audio digital outputs 1 & 2 (CGB) - pub const PCM12: Address = 0xFF76; - /// Audio digital outputs 3 & 4 (CGB) - pub const PCM34: Address = 0xFF77; - - //------------------------------------------------------------------------- - // High RAM - //------------------------------------------------------------------------- - - /// Working & Stack RAM (127 bytes) - pub const HRAM: MemoryRange = 0xFF80..0xFFFF; - - //------------------------------------------------------------------------- - // Interrupts - //------------------------------------------------------------------------- - - /// Interrupt enable - pub const IE: Address = 0xFFFF; - /// Interrupt flag - pub const IF: Address = 0xFF0F; -} - -#[derive(Clone)] -pub struct Memory { - mem: Vec, - bootrom: &'static [u8; 256], - rom_first256bytes: Vec, - code_listing: Vec>, - pub buttons: Buttons, -} - -#[derive(Clone, Copy, Default, Debug)] -pub struct Buttons { - pub a: bool, - pub b: bool, - pub start: bool, - pub select: bool, - pub up: bool, - pub right: bool, - pub down: bool, - pub left: bool, -} - -impl PartialEq for Memory { - fn eq(&self, other: &Self) -> bool { - self.slice(map::WRAM) == other.slice(map::WRAM) - } -} - -impl Default for Memory { - fn default() -> Self { - Self::new() - } -} - -impl Memory { - pub fn new() -> Self { - const ARRAY_REPEAT_VALUE: Option = None; - Self { - mem: vec![0; 65536], - bootrom: include_bytes!("../dmg.bin"), - rom_first256bytes: vec![0; 256], - code_listing: vec![ARRAY_REPEAT_VALUE; 0xffff + 1], - buttons: Buttons::default(), - } - } - - pub fn array_ref(&self, from: Address) -> &[u8; N] { - self.mem[from..from + N].try_into().unwrap() // guaranteed to have size N - } - - pub fn slice(&self, range: MemoryRange) -> &[u8] { - &self.mem[range] - } - - pub fn slice_mut(&mut self, range: MemoryRange) -> &mut [u8] { - &mut self.mem[range] - } - - pub fn code_listing(&self) -> &[Option] { - &self.code_listing - } - - pub fn set_code_listing_at(&mut self, pc: u16, v: String) { - self.code_listing[pc as usize] = Some(v); - } -} - -#[derive(Clone, PartialEq)] -pub struct Bus(Rc>); - -impl Bus { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - Bus(Rc::new(RefCell::new(Memory::new()))) - } - - pub fn memory(&self) -> Ref { - self.0.borrow() - } - - pub fn memory_mut(&self) -> RefMut { - self.0.borrow_mut() - } - - pub fn load_bootrom(&mut self) { - self.memory_mut().rom_first256bytes = self.copy_range(0x0000..0x0100); - let bootrom = self.memory().bootrom; - self.clone_from_slice(map::BOOTROM, bootrom); - self.memory_mut().code_listing[map::BOOTROM].fill(None); - } - - pub fn unload_bootrom(&mut self) { - let backup = self.memory_mut().rom_first256bytes.clone(); - self.clone_from_slice(map::BOOTROM, &backup); - self.memory_mut().code_listing[map::BOOTROM].fill(None); - } - - pub fn load_cartridge(&mut self, cartridge: &[u8]) { - let l = cartridge.len(); - self.clone_from_slice(0x0000..l, &cartridge[0x0000..l]); - } - - pub fn read(&self, address: Address) -> u8 { - if address == map::JOYP { - self.joyp() - } else { - self.memory().mem[address as Address] - } - } - - pub fn write(&mut self, address: Address, value: u8) { - self.memory_mut().mem[address as Address] = value; - } - - fn _read(&self, address: Address) -> u8 { - if address == map::JOYP { - self.joyp() - } else { - self.memory().mem[address] - } - } - - fn _write(&mut self, address: Address, value: u8) { - if address == map::TAC { - println!("write to TAC: {}", value); - } - self.memory_mut().mem[address] = value; - } - - pub fn clone_from_slice(&mut self, range: MemoryRange, slice: &[u8]) { - self.memory_mut().mem[range.start..range.end].clone_from_slice(slice); - } - - pub fn copy_range(&self, range: MemoryRange) -> Vec { - self.memory_mut().mem[range.start..range.end].to_vec() - } - - pub fn with_slice(&self, range: MemoryRange, reader: impl FnOnce(&[u8]) -> T) -> T { - reader(&self.memory().mem[range]) - } - - /// Runs closure `reader` with access to a fixed-size slice of `N` bytes. - pub fn with_span( - &self, - start: Address, - reader: impl FnOnce(&[u8; N]) -> T, - ) -> T { - reader(self.memory().array_ref(start)) - } - - // registers - pub fn lcdc(&self) -> u8 { - self._read(map::LCDC) - } - - pub fn set_lcdc(&mut self, value: u8) { - self._write(map::LCDC, value); - } - - pub fn stat(&self) -> u8 { - self._read(map::STAT) - } - - pub fn set_stat(&mut self, value: u8) { - self._write(map::STAT, value); - } - - pub fn scy(&self) -> u8 { - self._read(map::SCY) - } - - pub fn set_scy(&mut self, value: u8) { - self._write(map::SCY, value); - } - - pub fn scx(&self) -> u8 { - self._read(map::SCX) - } - - pub fn set_scx(&mut self, value: u8) { - self._write(map::SCX, value); - } - - pub fn ly(&self) -> u8 { - self._read(map::LY) - } - - pub fn set_ly(&mut self, value: u8) { - self._write(map::LY, value); - } - - pub fn lyc(&self) -> u8 { - self._read(map::LYC) - } - - pub fn set_lyc(&mut self, value: u8) { - self._write(map::LYC, value) - } - - pub fn with_vram(&self, reader: impl FnOnce(&[u8]) -> R) -> R { - reader(&self.memory().mem[map::VRAM]) - } - - fn joyp(&self) -> u8 { - let buttons = self.buttons(); - let joyp = self.memory().mem[map::JOYP]; - let sel_buttons = !bw::test_bit8::<5>(joyp); - let sel_dpad = !bw::test_bit8::<4>(joyp); - let b = if sel_dpad && sel_buttons { - 0 - } else if sel_dpad { - ((buttons.down as u8) << 3) - + ((buttons.up as u8) << 2) - + ((buttons.left as u8) << 1) - + (buttons.right as u8) - } else if sel_buttons { - ((buttons.start as u8) << 3) - + ((buttons.select as u8) << 2) - + ((buttons.b as u8) << 1) - + (buttons.a as u8) - } else { - 0 - }; - // Setting higher 2 bits (which are ignored) to 1 just because SameBoy does it too - ((joyp & 0xf0) + (!b & 0x0f)) | 0b1100_0000 - } - - pub fn buttons(&self) -> Buttons { - self.memory().buttons - } - - pub fn set_buttons(&mut self, buttons: &Buttons) { - self.memory_mut().buttons = *buttons; - } - - pub fn ie(&self) -> u8 { - self._read(map::IE) - } - - pub fn iflag(&self) -> u8 { - self._read(map::IF) - } - - pub fn set_iflag(&mut self, value: u8) { - self._write(map::IF, value) - } -} diff --git a/fpt/src/memory/cartridge.rs b/fpt/src/memory/cartridge.rs new file mode 100644 index 0000000..a2590dd --- /dev/null +++ b/fpt/src/memory/cartridge.rs @@ -0,0 +1,104 @@ +use crate::memory::map; +use crate::memory::{Address, MemoryRange}; + +pub fn get_rom_size(tape: &[u8]) -> u8 { + tape[map::ROM_SIZE] +} + +pub fn get_ram_size(tape: &[u8]) -> u8 { + tape[map::RAM_SIZE] +} + +pub fn get_cartridge_type(tape: &[u8]) -> u8 { + tape[map::CARTRIDGE_TYPE] +} + +pub fn convert_rom_size(rom_size: u8) -> usize { + match rom_size { + 0x00 => 2, + 0x01 => 4, + 0x02 => 8, + 0x03 => 16, + 0x04 => 32, + 0x05 => 64, + 0x06 => 128, + 0x07 => 256, + 0x08 => 512, + 0x52 => 72, + 0x53 => 80, + 0x54 => 96, + _ => panic!(), + } +} + +pub fn convert_ram_size(ram_size: u8) -> usize { + match ram_size { + 0x00 => 0, + 0x02 => 1, + 0x03 => 4, + 0x04 => 16, + 0x05 => 8, + _ => panic!(), + } +} + +pub trait Cartridge { + fn read(&self, address: Address) -> u8; + fn write(&mut self, address: Address, value: u8); + + fn read_range(&self, memory_range: MemoryRange) -> Vec; + + fn get_title(&self) -> String { + String::from_utf8(self.read_range(map::TITLE)).unwrap() + } + + fn get_manufacturer_code(&self) -> String { + String::from_utf8(self.read_range(map::MANUFACTURER_CODE)).unwrap() + } + + fn get_new_licensee_code(&self) -> String { + String::from_utf8(self.read_range(map::NEW_LICENSEE_CODE)).unwrap() + } + + fn get_sgb_flag(&self) -> u8 { + self.read(map::SGB_FLAG) + } + + fn get_cartridge_type(&self) -> u8 { + self.read(map::CARTRIDGE_TYPE) + } + + fn get_rom_size(&self) -> u8 { + self.read(map::ROM_SIZE) + } + + fn get_ram_size(&self) -> u8 { + self.read(map::RAM_SIZE) + } + + fn get_old_licensee_code(&self) -> u8 { + self.read(map::OLD_LICENSEE_CODE) + } + + fn get_version_number(&self) -> u8 { + self.read(map::VERSION_NUMBER) + } +} + +pub struct EmptyCartridge {} + +impl EmptyCartridge { + pub fn new() -> EmptyCartridge { + EmptyCartridge {} + } +} +impl Cartridge for EmptyCartridge { + fn read(&self, _address: Address) -> u8 { + 0xFF + } + fn write(&mut self, _address: Address, _value: u8) {} + + fn read_range(&self, _memory_range: MemoryRange) -> Vec { + Vec::new() + } +} diff --git a/fpt/src/memory/lib.rs b/fpt/src/memory/lib.rs new file mode 100644 index 0000000..a87321e --- /dev/null +++ b/fpt/src/memory/lib.rs @@ -0,0 +1,301 @@ +use std::cell::{Ref, RefCell, RefMut}; +use std::ops::Range; +use std::rc::Rc; + +use crate::bw; +use crate::memory::map; +use crate::memory::{create_empty_mbc, create_mbc, Cartridge}; + +pub type Address = usize; +pub type MemoryRange = Range
; + +#[derive(Clone, Copy, Default, Debug)] +pub struct Buttons { + pub a: bool, + pub b: bool, + pub start: bool, + pub select: bool, + pub up: bool, + pub right: bool, + pub down: bool, + pub left: bool, +} + +#[derive(Clone)] +pub struct Memory { + mem: Vec, + cartridge: Rc>, + bootrom: &'static [u8; 256], + rom_first256bytes: Vec, + code_listing: Vec>, + pub buttons: Buttons, + bootrom_unloaded: bool, +} + +impl PartialEq for Memory { + fn eq(&self, other: &Self) -> bool { + self.slice(map::WRAM) == other.slice(map::WRAM) + } +} + +impl Default for Memory { + fn default() -> Self { + Self::new() + } +} + +impl Memory { + pub fn new() -> Self { + const ARRAY_REPEAT_VALUE: Option = None; + Self { + mem: vec![0; 65536], + cartridge: create_empty_mbc(), + bootrom: include_bytes!("../../dmg.bin"), + rom_first256bytes: vec![0; 256], + code_listing: vec![ARRAY_REPEAT_VALUE; 0xffff + 1], + buttons: Buttons::default(), + bootrom_unloaded: false, + } + } + + pub fn cartridge(&self) -> &Rc> { + &self.cartridge + } + + pub fn cartridge_mut(&mut self) -> &mut Rc> { + &mut self.cartridge + } + + pub fn set_cartridge(&mut self, cartridge: Rc>) { + self.cartridge = cartridge; + } + + pub fn array_ref(&self, from: Address) -> &[u8; N] { + self.mem[from..from + N].try_into().unwrap() // guaranteed to have size N + } + + pub fn slice(&self, range: MemoryRange) -> &[u8] { + &self.mem[range] + } + + pub fn slice_mut(&mut self, range: MemoryRange) -> &mut [u8] { + &mut self.mem[range] + } + + pub fn code_listing(&self) -> &[Option] { + &self.code_listing + } + + pub fn set_code_listing_at(&mut self, pc: u16, v: String) { + self.code_listing[pc as usize] = Some(v); + } + + pub fn unload_bootrom(&mut self) { + self.bootrom_unloaded = true; + } + + pub fn bootrom_unloaded(&self) -> bool { + self.bootrom_unloaded + } +} + +#[derive(Clone, PartialEq)] +pub struct Bus(Rc>); + +impl Bus { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Bus(Rc::new(RefCell::new(Memory::new()))) + } + + pub fn memory(&self) -> Ref { + self.0.borrow() + } + + pub fn memory_mut(&self) -> RefMut { + self.0.borrow_mut() + } + + pub fn load_bootrom(&mut self) { + self.memory_mut().rom_first256bytes = self.copy_range(0x0000..0x0100); + let bootrom = self.memory().bootrom; + self.clone_from_slice(map::BOOTROM, bootrom); + self.memory_mut().code_listing[map::BOOTROM].fill(None); + } + + pub fn unload_bootrom(&mut self) { + let backup = self.memory().rom_first256bytes.clone(); + self.clone_from_slice(map::BOOTROM, &backup); + self.memory_mut().code_listing[map::BOOTROM].fill(None); + } + + pub fn load_cartridge(&mut self, cartridge: &[u8]) { + self.memory_mut().set_cartridge(create_mbc(cartridge)); + } + + pub fn read(&self, address: Address) -> u8 { + if map::BOOTROM.contains(&address) && !self.memory().bootrom_unloaded() { + self.memory().bootrom[address] + } else if map::ROM_BANK0.contains(&address) + || map::ROM_BANK1.contains(&address) + || map::EXT_RAM.contains(&address) + { + self.memory().cartridge().borrow().read(address) + } else if address == map::JOYP { + self.joyp() + } else { + self.memory().mem[address as Address] + } + } + + pub fn write(&mut self, address: Address, value: u8) { + if map::ROM_BANK0.contains(&address) + || map::ROM_BANK1.contains(&address) + || map::EXT_RAM.contains(&address) + { + self.memory_mut() + .cartridge_mut() + .borrow_mut() + .write(address, value); + } else { + self.memory_mut().mem[address as Address] = value; + } + } + + fn _read(&self, address: Address) -> u8 { + if address == map::JOYP { + self.joyp() + } else { + self.memory().mem[address] + } + } + + fn _write(&mut self, address: Address, value: u8) { + if address == map::TAC { + println!("write to TAC: {}", value); + } + self.memory_mut().mem[address] = value; + } + + pub fn clone_from_slice(&mut self, mut range: MemoryRange, slice: &[u8]) { + if range.end > 65535 { + range.end = 65535; + } + + let slice = slice.to_vec(); + + let slice = &slice[0..(range.end - range.start)]; + self.memory_mut().mem[range.start..range.end].clone_from_slice(slice); + } + + pub fn copy_range(&self, range: MemoryRange) -> Vec { + self.memory_mut().mem[range.start..range.end].to_vec() + } + + pub fn with_slice(&self, range: MemoryRange, reader: impl FnOnce(&[u8]) -> T) -> T { + reader(&self.memory().mem[range]) + } + + /// Runs closure `reader` with access to a fixed-size slice of `N` bytes. + pub fn with_span( + &self, + start: Address, + reader: impl FnOnce(&[u8; N]) -> T, + ) -> T { + reader(self.memory().array_ref(start)) + } + + // registers + pub fn lcdc(&self) -> u8 { + self._read(map::LCDC) + } + + pub fn set_lcdc(&mut self, value: u8) { + self._write(map::LCDC, value); + } + + pub fn stat(&self) -> u8 { + self._read(map::STAT) + } + + pub fn set_stat(&mut self, value: u8) { + self._write(map::STAT, value); + } + + pub fn scy(&self) -> u8 { + self._read(map::SCY) + } + + pub fn set_scy(&mut self, value: u8) { + self._write(map::SCY, value); + } + + pub fn scx(&self) -> u8 { + self._read(map::SCX) + } + + pub fn set_scx(&mut self, value: u8) { + self._write(map::SCX, value); + } + + pub fn ly(&self) -> u8 { + self._read(map::LY) + } + + pub fn set_ly(&mut self, value: u8) { + self._write(map::LY, value); + } + + pub fn lyc(&self) -> u8 { + self._read(map::LYC) + } + + pub fn set_lyc(&mut self, value: u8) { + self._write(map::LYC, value) + } + + pub fn with_vram(&self, reader: impl FnOnce(&[u8]) -> R) -> R { + reader(&self.memory().mem[map::VRAM]) + } + + fn joyp(&self) -> u8 { + let buttons = self.buttons(); + let joyp = self.memory().mem[map::JOYP]; + let sel_buttons = !bw::test_bit8::<5>(joyp); + let sel_dpad = !bw::test_bit8::<4>(joyp); + let b = if sel_dpad { + ((buttons.down as u8) << 3) + + ((buttons.up as u8) << 2) + + ((buttons.left as u8) << 1) + + (buttons.right as u8) + } else if sel_buttons { + ((buttons.start as u8) << 3) + + ((buttons.select as u8) << 2) + + ((buttons.b as u8) << 1) + + (buttons.a as u8) + } else { + 0 + }; + (joyp & 0xf0) + (!b & 0x0f) + } + + pub fn buttons(&self) -> Buttons { + self.memory().buttons + } + + pub fn set_buttons(&mut self, buttons: &Buttons) { + self.memory_mut().buttons = *buttons; + } + + pub fn ie(&self) -> u8 { + self._read(map::IE) + } + + pub fn iflag(&self) -> u8 { + self._read(map::IF) + } + + pub fn set_iflag(&mut self, value: u8) { + self._write(map::IF, value) + } +} diff --git a/fpt/src/memory/map.rs b/fpt/src/memory/map.rs new file mode 100644 index 0000000..f61d3d0 --- /dev/null +++ b/fpt/src/memory/map.rs @@ -0,0 +1,214 @@ +/// You can access these consts like this: +/// ``` +/// assert_eq!(fpt::memory::map::ROM_DATA.start, 0x0100); +/// ``` +use super::{Address, MemoryRange}; + +//------------------------------------------------------------------------- +// Memory map +//------------------------------------------------------------------------- + +/// This is where the bootrom lives +pub const BOOTROM: MemoryRange = 0x0000..0x0100; + +/// The Cartridge Header +pub const ROM_DATA: MemoryRange = 0x0100..0x0150; + +/// User Program Area (32 KB) +/// From cartridge, usually a fixed bank +pub const ROM_BANK0: MemoryRange = 0x0000..0x4000; +/// From cartridge, switchable bank via mapper (if any) pub const USER_PROGRAM: MemoryRange = 0x0000..0x8000; +pub const ROM_BANK1: MemoryRange = 0x4000..0x8000; + +/// Video RAM (8 KB) - In CGB mode, switchable bank 0/1 +pub const VRAM: MemoryRange = 0x8000..0xA000; + +/// External Expansion Working RAM (8 KB) - From cartridge, switchable bank if any +pub const EXT_RAM: MemoryRange = 0xA000..0xC000; + +/// Unit Working RAM (8 KB) +pub const WRAM: MemoryRange = 0xC000..0xE000; + +/// Not usable (Mirror of C000~DDFF (ECHO RAM)) +pub const NOT_USABLE1: MemoryRange = 0xE000..0xFE00; + +/// Object Attribute Memory (40 OBJs, 40 x 32 bits) +pub const OAM: MemoryRange = 0xFE00..0xFEA0; + +/// Not usable +pub const NOT_USABLE2: MemoryRange = 0xFEA0..0xFF00; + +//------------------------------------------------------------------------- +// I/O Registers +//------------------------------------------------------------------------- + +/// Joypad +pub const JOYP: Address = 0xFF00; +/// Serial transfer data +pub const SB: Address = 0xFF01; +/// Serial transfer control +pub const SC: Address = 0xFF02; +/// Divider register +pub const DIV: Address = 0xFF04; +/// Timer counter +pub const TIMA: Address = 0xFF05; +/// Timer modulo +pub const TMA: Address = 0xFF06; +/// Timer control +pub const TAC: Address = 0xFF07; + +//------------------------------------------------------------------------- +// I/O: Sound +//------------------------------------------------------------------------- + +/// Sound channel 1 sweep +pub const NR10: Address = 0xFF10; +/// Sound channel 1 length timer & duty cycle +pub const NR11: Address = 0xFF11; +/// Sound channel 1 volume & envelope +pub const NR12: Address = 0xFF12; +/// Sound channel 1 period low +pub const NR13: Address = 0xFF13; +/// Sound channel 1 period high & control +pub const NR14: Address = 0xFF14; +/// Sound channel 2 length timer & duty cycle +pub const NR21: Address = 0xFF16; +/// Sound channel 2 volume & envelope +pub const NR22: Address = 0xFF17; +/// Sound channel 2 period low +pub const NR23: Address = 0xFF18; +/// Sound channel 2 period high & control +pub const NR24: Address = 0xFF19; +/// Sound channel 3 DAC enable +pub const NR30: Address = 0xFF1A; +/// Sound channel 3 length timer +pub const NR31: Address = 0xFF1B; +/// Sound channel 3 output level +pub const NR32: Address = 0xFF1C; +/// Sound channel 3 period low +pub const NR33: Address = 0xFF1D; +/// Sound channel 3 period high & control +pub const NR34: Address = 0xFF1E; +/// Sound channel 4 length timer +pub const NR41: Address = 0xFF20; +/// Sound channel 4 volume & envelope +pub const NR42: Address = 0xFF21; +/// Sound channel 4 frequency & randomness +pub const NR43: Address = 0xFF22; +/// Sound channel 4 control +pub const NR44: Address = 0xFF23; +/// Master volume & VIN panning +pub const NR50: Address = 0xFF24; +/// Sound panning +pub const NR51: Address = 0xFF25; +/// Sound on/off +pub const NR52: Address = 0xFF26; +/// Wave RAM +pub const WAVE_RAM: MemoryRange = 0xFF30..0xFF40; + +//------------------------------------------------------------------------- +// IO: PPU +//------------------------------------------------------------------------- + +/// LCD control +pub const LCDC: Address = 0xFF40; +/// LCD status +pub const STAT: Address = 0xFF41; +/// Viewport Y position +pub const SCY: Address = 0xFF42; +/// Viewport X position +pub const SCX: Address = 0xFF43; +/// LCD Y coordinate +pub const LY: Address = 0xFF44; +/// LY compare +pub const LYC: Address = 0xFF45; +/// OAM DMA source address & start +pub const DMA: Address = 0xFF46; +/// BG palette data (DMG) +pub const BGP: Address = 0xFF47; +/// OBJ palette 0 data (DMG) +pub const OBP0: Address = 0xFF48; +/// OBJ palette 1 data (DMG) +pub const OBP1: Address = 0xFF49; +/// Window Y position +pub const WY: Address = 0xFF4A; +/// Window X position plus 7 +pub const WX: Address = 0xFF4B; + +/// BANK register: Set to non-zero to disable boot ROM +pub const BANK: Address = 0xFF50; + +//------------------------------------------------------------------------- +// CGB extra +// https://gbdev.io/pandocs/CGB_Registers.html +//------------------------------------------------------------------------- + +/// Prepare speed switch (CGB) +pub const KEY1: Address = 0xFF4C; +/// VRAM bank (CGB) +pub const VBK: Address = 0xFF4F; +/// VRAM DMA source high (CGB) +pub const HDMA1: Address = 0xFF51; +/// VRAM DMA source low (CGB) +pub const HDMA2: Address = 0xFF52; +/// VRAM DMA destination high (CGB) +pub const HDMA3: Address = 0xFF53; +/// VRAM DMA destination low (CGB) +pub const HDMA4: Address = 0xFF54; +/// VRAM DMA length/mode/start (CGB) +pub const HDMA5: Address = 0xFF55; +/// Infrared communications port (GGB) +pub const RP: Address = 0xFF56; +/// Background color palette specification / Background palette index (CGB) +pub const BCPS: Address = 0xFF68; +/// Background color palette data / Background palette data (CGB) +pub const BCPD: Address = 0xFF69; +/// OBJ color palette specification / OBJ palette index (CGB) +pub const OCPS: Address = 0xFF6A; +/// OBJ color palette data / OBJ palette data (CGB) +pub const OCPD: Address = 0xFF6B; +/// Object priority mode (CGB) +pub const OPRI: Address = 0xFF6C; +/// WRAM bank (CGB) pub const SVBK: Address = 0xFF70; +/// Audio digital outputs 1 & 2 (CGB) +pub const PCM12: Address = 0xFF76; +/// Audio digital outputs 3 & 4 (CGB) +pub const PCM34: Address = 0xFF77; + +//------------------------------------------------------------------------- +// High RAM +//------------------------------------------------------------------------- + +/// Working & Stack RAM (127 bytes) +pub const HRAM: MemoryRange = 0xFF80..0xFFFF; + +//------------------------------------------------------------------------- +// Interrupts +//------------------------------------------------------------------------- + +/// Interrupt enable +pub const IE: Address = 0xFFFF; +/// Interrupt flag +pub const IF: Address = 0xFF0F; + +/// Cartridge sections +/// Game title - upper case ascii - 16 bytes +pub const TITLE: MemoryRange = 0x134..0x144; +/// Unknown manufacturer code - 4 bytes +pub const MANUFACTURER_CODE: MemoryRange = 0x13F..0x143; +/// Color game boy flag +pub const CGB_FLAG: Address = 0x143; +/// Valid when old licensee is 0x33 - 2 bytes +pub const NEW_LICENSEE_CODE: MemoryRange = 0x144..0x146; +/// Super game boy flag +pub const SGB_FLAG: Address = 0x146; +/// Cartridge type including memory mapper +pub const CARTRIDGE_TYPE: Address = 0x147; +/// Number of rom banks = 2^(ROM_SIZE+1) +pub const ROM_SIZE: Address = 0x148; +/// Number of ram banks +pub const RAM_SIZE: Address = 0x149; +/// Old licensee code +pub const OLD_LICENSEE_CODE: Address = 0x14b; +/// Game version +pub const VERSION_NUMBER: Address = 0x14c; diff --git a/fpt/src/memory/mbc1.rs b/fpt/src/memory/mbc1.rs new file mode 100644 index 0000000..9b0ecea --- /dev/null +++ b/fpt/src/memory/mbc1.rs @@ -0,0 +1,84 @@ +use super::cartridge::{convert_ram_size, convert_rom_size, get_ram_size, get_rom_size}; +use super::{map, Address, Cartridge, MemoryRange}; + +pub struct Mbc1Cartridge { + memory: Vec, + rom_banks: Vec<[u8; 0x4000]>, + ram_banks: Vec<[u8; 0x2000]>, + ext_ram_enabled: bool, + rom_bank_number: usize, + ram_bank_number: usize, +} + +impl Mbc1Cartridge { + pub fn new(cartridge: &[u8]) -> Mbc1Cartridge { + let rom_size = convert_rom_size(get_rom_size(cartridge)); + let ram_size = convert_ram_size(get_ram_size(cartridge)); + let mut rom_banks = vec![[0; 0x4000]; rom_size as usize]; + let mut ram_banks = vec![[0; 0x2000]; ram_size as usize]; + + // TODO: wtf is this initialization + for i in 0..rom_size { + for j in 0..0x4000 { + rom_banks[i][j] = cartridge[0x4000 * i + j]; + } + } + + for i in 0..ram_size { + for j in 0..0x2000 { + ram_banks[i][j] = cartridge[0x2000 * i + j]; + } + } + + Mbc1Cartridge { + memory: cartridge.to_vec(), + rom_banks, + ram_banks, + ext_ram_enabled: false, + rom_bank_number: 0, + ram_bank_number: 0, + } + } +} + +impl Cartridge for Mbc1Cartridge { + fn read(&self, address: Address) -> u8 { + if map::EXT_RAM.contains(&address) && !self.ext_ram_enabled { + 0 // TODO: check that disabled ram reads 0 + } else if map::EXT_RAM.contains(&address) && self.ext_ram_enabled { + self.ram_banks[self.ram_bank_number][address - map::EXT_RAM.start] + } else if map::ROM_BANK0.contains(&address) { + self.rom_banks[0][address - map::ROM_BANK0.start] + } else if map::ROM_BANK1.contains(&address) { + self.rom_banks[self.rom_bank_number][address - map::ROM_BANK1.start] + } else { + self.memory[address] + } + } + fn write(&mut self, address: Address, value: u8) { + if (0x0000..0x2000).contains(&address) { + self.ext_ram_enabled = value & 0xF == 0xA; + } else if (0x2000..0x4000).contains(&address) { + // TODO: rom bank number upper bits + let rom_bank_number = value & 0x1F; + if rom_bank_number == 0 { + self.rom_bank_number = 1; + } else { + self.rom_bank_number = rom_bank_number as usize; // TODO: needs to be masked to log2(#banks) + } + } else if (0x4000..0x6000).contains(&address) { + let ram_bank_number = value & 0x3; + self.ram_bank_number = ram_bank_number as usize; // TODO: needs to be checked for number of ram + // banks + } else if map::EXT_RAM.contains(&address) && self.ext_ram_enabled { + self.memory[address] = value; + } + } + + fn read_range(&self, memory_range: MemoryRange) -> Vec { + memory_range + .into_iter() + .map(|address| self.read(address)) + .collect() + } +} diff --git a/fpt/src/memory/mbc3.rs b/fpt/src/memory/mbc3.rs new file mode 100644 index 0000000..c207139 --- /dev/null +++ b/fpt/src/memory/mbc3.rs @@ -0,0 +1,84 @@ +use super::cartridge::{convert_ram_size, convert_rom_size, get_ram_size, get_rom_size}; +use super::{map, Address, Cartridge, MemoryRange}; + +pub struct Mbc3Cartridge { + memory: Vec, + rom_banks: Vec<[u8; 0x4000]>, + ram_banks: Vec<[u8; 0x2000]>, + ext_ram_enabled: bool, + rom_bank_number: usize, + ram_bank_number: usize, +} + +impl Mbc3Cartridge { + pub fn new(cartridge: &[u8]) -> Mbc3Cartridge { + let rom_size = convert_rom_size(get_rom_size(cartridge)); + let ram_size = convert_ram_size(get_ram_size(cartridge)); + let mut rom_banks = vec![[0; 0x4000]; rom_size as usize]; + let mut ram_banks = vec![[0; 0x2000]; ram_size as usize]; + + // TODO: wtf is this initialization + for i in 0..rom_size { + for j in 0..0x4000 { + rom_banks[i][j] = cartridge[0x4000 * i + j]; + } + } + + for i in 0..ram_size { + for j in 0..0x2000 { + ram_banks[i][j] = cartridge[0x2000 * i + j]; + } + } + + Mbc3Cartridge { + memory: cartridge.to_vec(), + rom_banks, + ram_banks, + ext_ram_enabled: false, + rom_bank_number: 0, + ram_bank_number: 0, + } + } +} + +impl Cartridge for Mbc3Cartridge { + fn read(&self, address: Address) -> u8 { + if map::EXT_RAM.contains(&address) && !self.ext_ram_enabled { + 0 // TODO: check that disabled ram reads 0 + } else if map::EXT_RAM.contains(&address) && self.ext_ram_enabled { + self.ram_banks[self.ram_bank_number][address - map::EXT_RAM.start] + } else if map::ROM_BANK0.contains(&address) { + self.rom_banks[0][address - map::ROM_BANK0.start] + } else if map::ROM_BANK1.contains(&address) { + self.rom_banks[self.rom_bank_number][address - map::ROM_BANK1.start] + } else { + self.memory[address] + } + } + fn write(&mut self, address: Address, value: u8) { + if (0x0000..0x2000).contains(&address) { + self.ext_ram_enabled = value & 0xF == 0xA; + } else if (0x2000..0x4000).contains(&address) { + // TODO: rom bank number upper bits + let rom_bank_number = value & 0x1F; + if rom_bank_number == 0 { + self.rom_bank_number = 1; + } else { + self.rom_bank_number = rom_bank_number as usize; // TODO: needs to be masked to log2(#banks) + } + } else if (0x4000..0x6000).contains(&address) { + let ram_bank_number = value & 0x3; + self.ram_bank_number = ram_bank_number as usize; // TODO: needs to be checked for number of ram + // banks + } else if map::EXT_RAM.contains(&address) && self.ext_ram_enabled { + self.memory[address] = value; + } + } + + fn read_range(&self, memory_range: MemoryRange) -> Vec { + memory_range + .into_iter() + .map(|address| self.read(address)) + .collect() + } +} diff --git a/fpt/src/memory/mbc_builder.rs b/fpt/src/memory/mbc_builder.rs new file mode 100644 index 0000000..89746dd --- /dev/null +++ b/fpt/src/memory/mbc_builder.rs @@ -0,0 +1,23 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use super::cartridge::{get_cartridge_type, EmptyCartridge}; +use super::mbc1::Mbc1Cartridge; +use super::mbc3::Mbc3Cartridge; +use super::mbc_none::NoMbcCartridge; +use super::Cartridge; + +pub fn create_mbc(cartridge_data: &[u8]) -> Rc> { + let cartridge_type = get_cartridge_type(cartridge_data); + + match dbg!(cartridge_type) { + 0x00 => Rc::new(RefCell::new(NoMbcCartridge::new(cartridge_data))), + 0x01..0x03 => Rc::new(RefCell::new(Mbc1Cartridge::new(cartridge_data))), + 0x0F..0x13 => Rc::new(RefCell::new(Mbc3Cartridge::new(cartridge_data))), + _ => panic!(), + } +} + +pub fn create_empty_mbc() -> Rc> { + Rc::new(RefCell::new(EmptyCartridge::new())) +} diff --git a/fpt/src/memory/mbc_none.rs b/fpt/src/memory/mbc_none.rs new file mode 100644 index 0000000..1745081 --- /dev/null +++ b/fpt/src/memory/mbc_none.rs @@ -0,0 +1,28 @@ +use super::cartridge::Cartridge; +use super::{Address, MemoryRange}; + +pub struct NoMbcCartridge { + memory: Vec, +} + +impl NoMbcCartridge { + pub fn new(cartridge: &[u8]) -> NoMbcCartridge { + let mut cartridge = cartridge.to_vec(); + let mut padding = vec![0; (0x10000 - cartridge.len()).max(0)]; + cartridge.append(&mut padding); + NoMbcCartridge { memory: cartridge } + } +} + +impl Cartridge for NoMbcCartridge { + fn read(&self, address: Address) -> u8 { + self.memory[address] + } + fn write(&mut self, address: Address, value: u8) { + self.memory[address] = value; + } + + fn read_range(&self, memory_range: MemoryRange) -> Vec { + self.memory[memory_range].to_vec() + } +} diff --git a/fpt/src/memory/mod.rs b/fpt/src/memory/mod.rs new file mode 100644 index 0000000..0ba0440 --- /dev/null +++ b/fpt/src/memory/mod.rs @@ -0,0 +1,12 @@ +mod cartridge; +mod lib; +pub mod map; +mod mbc1; +mod mbc3; +mod mbc_builder; +mod mbc_none; + +pub use cartridge::Cartridge; +pub use lib::{Address, Bus, Buttons, MemoryRange}; +pub use mbc_builder::{create_empty_mbc, create_mbc}; +pub use mbc_none::NoMbcCartridge; diff --git a/fpt/tests/rom_tests.json b/fpt/tests/rom_tests.json index 87686d4..46baea1 100644 --- a/fpt/tests/rom_tests.json +++ b/fpt/tests/rom_tests.json @@ -246,12 +246,16 @@ { "id":44, "path":"../target/test_roms/mooneye/acceptance/if_ie_registers.gb", - "termination_address":"0x4ab4" + "termination_address":"0x4ab4", + "passing": false, + "enabled": false }, { "id":45, "path":"../target/test_roms/mooneye/acceptance/intr_timing.gb", - "termination_address":"0x4ab4" + "termination_address":"0x4ab4", + "passing": false, + "enabled": false }, { "id":46, @@ -309,7 +313,9 @@ { "id":55, "path":"../target/test_roms/mooneye/acceptance/ret_cc_timing.gb", - "termination_address":"0x4ab4" + "termination_address":"0x4ab4", + "passing": false, + "enabled": false }, { "id":56, @@ -321,17 +327,22 @@ { "id":57, "path":"../target/test_roms/mooneye/acceptance/reti_timing.gb", - "termination_address":"0x4ab4" + "termination_address":"0x4ab4", + "passing": false, + "enabled": false }, { "id":58, "path":"../target/test_roms/mooneye/acceptance/ret_timing.gb", - "termination_address":"0x4ab4" + "termination_address":"0x4ab4", + "passing": false, + "enabled": false }, { "id":59, "path":"../target/test_roms/mooneye/acceptance/rst_timing.gb", - "termination_address":"0x4ab4" + "termination_address":"0x4ab4", + "enabled": false } ] }