use crate::{debugln, pad::Pad, ppu::Ppu, rom::Cartridge, timer::Timer}; pub const BOOT_SIZE: usize = 256; pub const RAM_SIZE: usize = 8192; pub struct Mmu { /// Register that controls the interrupts that are considered /// to be enabled and should be triggered. pub ie: u8, /// Reference to the PPU (Pixel Processing Unit) that is going /// to be used both for VRAM reading/writing and to forward /// some of the access operations. ppu: Ppu, /// Reference to the Game Pad structure that is going to control /// the I/O access to this device. pad: Pad, timer: Timer, /// The cartridge ROM that is currently loaded into the system, /// going to be used to access ROM and external RAM banks. rom: Cartridge, boot_active: bool, boot: [u8; BOOT_SIZE], ram: [u8; RAM_SIZE], } impl Mmu { pub fn new(ppu: Ppu, pad: Pad, timer: Timer) -> Self { Self { ppu: ppu, pad: pad, timer: timer, rom: Cartridge::new(), boot_active: true, boot: [0u8; BOOT_SIZE], ram: [0u8; RAM_SIZE], ie: 0x0, } } pub fn reset(&mut self) { self.rom = Cartridge::new(); self.boot_active = true; self.boot = [0u8; BOOT_SIZE]; self.ram = [0u8; RAM_SIZE]; } pub fn ppu(&mut self) -> &mut Ppu { &mut self.ppu } pub fn pad(&mut self) -> &mut Pad { &mut self.pad } pub fn timer(&mut self) -> &mut Timer { &mut self.timer } pub fn boot_active(&self) -> bool { self.boot_active } pub fn read(&mut self, addr: u16) -> u8 { match addr & 0xf000 { // BOOT (256 B) + ROM0 (4 KB/16 KB) 0x0000 => { // in case the boot mode is active and the // address is withing boot memory reads from it if self.boot_active && addr <= 0x00fe { // if we're reading from this location we can // safely assume that we're exiting the boot // loading sequence and disable boot if addr == 0x00fe { self.boot_active = false; } return self.boot[addr as usize]; } self.rom.read(addr) } // ROM 0 (12 KB/16 KB) 0x1000 | 0x2000 | 0x3000 => self.rom.read(addr), // ROM 1 (Unbanked) (16 KB) 0x4000 | 0x5000 | 0x6000 | 0x7000 => self.rom.read(addr), // Graphics: VRAM (8 KB) 0x8000 | 0x9000 => self.ppu.vram[(addr & 0x1fff) as usize], // External RAM (8 KB) 0xa000 | 0xb000 => self.rom.read(addr), // Working RAM (8 KB) 0xc000 | 0xd000 => self.ram[(addr & 0x1fff) as usize], // Working RAM Shadow 0xe000 => self.ram[(addr & 0x1fff) as usize], // Working RAM Shadow, I/O, Zero-page RAM 0xf000 => match addr & 0x0f00 { 0x000 | 0x100 | 0x200 | 0x300 | 0x400 | 0x500 | 0x600 | 0x700 | 0x800 | 0x900 | 0xa00 | 0xb00 | 0xc00 | 0xd00 => self.ram[(addr & 0x1fff) as usize], 0xe00 => self.ppu.oam[(addr & 0x009f) as usize], 0xf00 => match addr & 0x00ff { 0x0f => { let value = if self.ppu.int_vblank() { 0x01 } else { 0x00 } | if self.timer.int_tima() { 0x04 } else { 0x00 }; value } 0x80..=0xfe => self.ppu.hram[(addr & 0x007f) as usize], 0xff => self.ie, _ => match addr & 0x00f0 { 0x00 => match addr & 0x00ff { 0x00 => self.pad.read(addr), 0x04..=0x07 => self.timer.read(addr), _ => { debugln!("Reading from unknown IO control 0x{:04x}", addr); 0x00 } }, 0x40 | 0x50 | 0x60 | 0x70 => self.ppu.read(addr), _ => { debugln!("Reading from unknown IO control 0x{:04x}", addr); 0x00 } }, }, addr => panic!("Reading from unknown location 0x{:04x}", addr), }, addr => panic!("Reading from unknown location 0x{:04x}", addr), } } pub fn write(&mut self, addr: u16, value: u8) { match addr & 0xf000 { // BOOT (256 B) + ROM0 (4 KB/16 KB) 0x0000 => self.rom.write(addr, value), // ROM 0 (12 KB/16 KB) 0x1000 | 0x2000 | 0x3000 => self.rom.write(addr, value), // ROM 1 (Unbanked) (16 KB) 0x4000 | 0x5000 | 0x6000 | 0x7000 => self.rom.write(addr, value), // Graphics: VRAM (8 KB) 0x8000 | 0x9000 => { self.ppu.vram[(addr & 0x1fff) as usize] = value; if addr < 0x9800 { self.ppu.update_tile(addr, value); } } // External RAM (8 KB) 0xa000 | 0xb000 => self.rom.write(addr, value), // Working RAM (8 KB) 0xc000 | 0xd000 => self.ram[(addr & 0x1fff) as usize] = value, // Working RAM Shadow 0xe000 => self.ram[(addr & 0x1fff) as usize] = value, // Working RAM Shadow, I/O, Zero-page RAM 0xf000 => match addr & 0x0f00 { 0x000 | 0x100 | 0x200 | 0x300 | 0x400 | 0x500 | 0x600 | 0x700 | 0x800 | 0x900 | 0xa00 | 0xb00 | 0xc00 | 0xd00 => { self.ram[(addr & 0x1fff) as usize] = value; } 0xe00 => { self.ppu.oam[(addr & 0x009f) as usize] = value; self.ppu.update_object(addr, value); } 0xf00 => match addr & 0x00ff { 0x0f => { self.ppu.set_int_vblank(value & 0x01 == 0x01); self.ppu.set_int_stat(value & 0x02 == 0x02); self.timer.set_int_tima(value & 0x04 == 0x04); } 0x80..=0xfe => self.ppu.hram[(addr & 0x007f) as usize] = value, 0xff => self.ie = value, _ => { match addr & 0x00f0 { 0x00 => match addr & 0x00ff { 0x00 => self.pad.write(addr, value), 0x04..=0x07 => self.timer.write(addr, value), _ => debugln!("Writing to unknown IO control 0x{:04x}", addr), }, 0x40 | 0x60 | 0x70 => { match addr & 0x00ff { 0x0046 => { // @todo must increment the cycle count by 160 // and make this a separated dma.rs file debugln!("Going to start DMA transfer to 0x{:x}00", value); let data = self.read_many((value as u16) << 8, 160); self.write_many(0xfe00, &data); } _ => self.ppu.write(addr, value), } } 0x50 => match addr & 0x00ff { 0x50 => self.boot_active = false, _ => debugln!("Writing to unknown IO control 0x{:04x}", addr), }, _ => debugln!("Writing to unknown IO control 0x{:04x}", addr), } } }, addr => panic!("Writing to unknown location 0x{:04x}", addr), }, addr => panic!("Writing to unknown location 0x{:04x}", addr), } } pub fn write_many(&mut self, addr: u16, data: &Vec<u8>) { for index in 0..data.len() { self.write(addr + index as u16, data[index]) } } pub fn read_many(&mut self, addr: u16, count: u16) -> Vec<u8> { let mut data: Vec<u8> = vec![]; for index in 0..count { let byte = self.read(addr + index); data.push(byte); } return data; } pub fn write_boot(&mut self, addr: u16, buffer: &[u8]) { self.boot[addr as usize..addr as usize + buffer.len()].clone_from_slice(buffer); } pub fn write_ram(&mut self, addr: u16, buffer: &[u8]) { self.ram[addr as usize..addr as usize + buffer.len()].clone_from_slice(buffer); } pub fn rom(&mut self) -> &mut Cartridge { &mut self.rom } pub fn set_rom(&mut self, rom: Cartridge) { self.rom = rom; } }