diff --git a/core/src/cpu/disassembler.rs b/core/src/cpu/disassembler.rs index e96e7ce..a5506fa 100644 --- a/core/src/cpu/disassembler.rs +++ b/core/src/cpu/disassembler.rs @@ -2,11 +2,12 @@ use crate::cpu::op::AddressingMode::*; use crate::cpu::op::{AddressingMode, Instruction}; use crate::cpu::Cpu; use simplelog::{trace}; +use crate::MemoryBus; impl Cpu { pub fn disassemble_next_instr(&self) { let registers = self.registers; - let op_code = self.memory_bus.borrow().get_byte(registers.pc) as usize; + let op_code = self.memory_bus.borrow().read_byte(registers.pc) as usize; let instr: Instruction = Self::INSTRUCTIONS[op_code]; let operation = instr.op().to_string(); diff --git a/core/src/memory.rs b/core/src/cpu/memory.rs similarity index 68% rename from core/src/memory.rs rename to core/src/cpu/memory.rs index 9d51576..9da9630 100644 --- a/core/src/memory.rs +++ b/core/src/cpu/memory.rs @@ -1,7 +1,8 @@ -use std::cell::RefCell; use crate::mappers::Mapper; +use crate::{MemoryBus, Observable, Observer}; use simplelog::warn; -use std::rc::{Weak}; +use std::cell::RefCell; +use std::rc::Weak; const MEMORY_MAX_ADDR: u16 = 0xFFFF; const PPU_MAX_ADDR: u16 = 0x4000; @@ -11,14 +12,14 @@ const CARTRIDGE_RAM_MAX_ADDR: u16 = 0x8000; const RAM_SIZE: usize = 0x0800; const RAM_MAX_ADDR: u16 = 0x2000; -pub struct MemoryBus { +pub struct CpuMemoryBus { mapper: Option>, ram: [u8; RAM_SIZE], - observers: Vec>>, + observers: Vec>>>, } -impl MemoryBus { +impl CpuMemoryBus { pub fn new() -> Self { let ram = [0; RAM_SIZE]; Self { @@ -33,11 +34,29 @@ impl MemoryBus { self.mapper = Some(mapper); } - /// Gets a byte from memory. + /// Gets a word from memory. /// /// # Arguments - /// * `addr` - The address of the byte - pub fn get_byte(&self, addr: u16) -> u8 { + /// * `addr` - The address of the word + pub fn get_word(&self, addr: u16) -> u16 { + let low = self.read_byte(addr) as u16; + let high = self.read_byte(addr + 1) as u16; + + (high << 8) | low + } + + fn notify_observers(&self, addr: u16, value: u8) { + for observer in self.observers.iter() { + match observer.upgrade() { + None => {} + Some(rc) => rc.borrow_mut().notify_updated(addr, value), + } + } + } +} + +impl MemoryBus for CpuMemoryBus { + fn read_byte(&self, addr: u16) -> u8 { assert!(addr < MEMORY_MAX_ADDR); if addr < RAM_MAX_ADDR { let ram_addr = (addr as usize) % RAM_SIZE; @@ -60,7 +79,7 @@ impl MemoryBus { if self.mapper.is_none() { warn!( - "Tried to read memory address {} before setting a mapper", + "Tried to read CPU memory address {} before setting a mapper", addr ); return 0; @@ -71,23 +90,7 @@ impl MemoryBus { } } - /// Gets a word from memory. - /// - /// # Arguments - /// * `addr` - The address of the word - pub fn get_word(&self, addr: u16) -> u16 { - let low = self.get_byte(addr) as u16; - let high = self.get_byte(addr + 1) as u16; - - (high << 8) | low - } - - /// Sets a byte in memory - /// - /// # Arguments - /// * `addr` - The address of the byte - /// * `value` - The value to set - pub fn set_byte(&mut self, addr: u16, value: u8) { + fn write_byte(&mut self, addr: u16, value: u8) { assert!(addr < MEMORY_MAX_ADDR); if addr < RAM_MAX_ADDR { let ram_addr = (addr as usize) % RAM_SIZE; @@ -106,28 +109,12 @@ impl MemoryBus { assert!(false); } - // Notify the observers - for observer in self.observers.iter() { - match observer.upgrade() { - None => {} - Some(rc) => rc.borrow_mut().notify_mem_byte_updated(addr, value) - } - } + self.notify_observers(addr, value); } - - pub fn register_observer(&mut self, observer: Weak>) { +} + +impl Observable for CpuMemoryBus { + fn register_observer(&mut self, observer: Weak>>) { self.observers.push(observer); } } - -// pub type MemoryObserver = impl Fn(u16, u8) -> (); - -// A memory observer. Will be notified when a byte was modified in memory. -pub trait MemoryObserver { - /// Called when a byte was updated in the memory bus. - /// - /// # Arguments - /// * addr - The address of the byte - /// * value - The updated value - fn notify_mem_byte_updated(&mut self, addr: u16, value: u8); -} diff --git a/core/src/cpu/mod.rs b/core/src/cpu/mod.rs index b958f48..3fe0af4 100644 --- a/core/src/cpu/mod.rs +++ b/core/src/cpu/mod.rs @@ -1,9 +1,10 @@ mod disassembler; mod op; mod operations; +pub mod memory; -use crate::memory::MemoryBus; -use crate::Clock; +use memory::CpuMemoryBus; +use crate::{Clock, MemoryBus}; use std::cell::RefCell; use std::rc::Rc; use bitflags::bitflags; @@ -19,7 +20,7 @@ pub struct Cpu { registers: CpuRegisters, /// The memory bus accessible by the CPU - memory_bus: Rc>, + memory_bus: Rc>, /// The number of cycles ran on the CPU cycle: usize, @@ -137,7 +138,7 @@ pub trait CpuInternals { } impl Cpu { - pub fn new(memory_bus: Rc>) -> Self { + pub fn new(memory_bus: Rc>) -> Self { Cpu { registers: CpuRegisters { pc: 0x8000, @@ -190,7 +191,7 @@ impl CpuInternals for Cpu { } fn program_get_next_byte(&mut self) -> u8 { - let byte = self.memory_bus.borrow().get_byte(self.registers.pc); + let byte = self.memory_bus.borrow().read_byte(self.registers.pc); self.registers.pc += 1; byte @@ -220,7 +221,7 @@ impl CpuInternals for Cpu { assert!(self.registers.sp > 0); let addr = STACK_ADDR | self.registers.sp as u16; - self.memory_bus.borrow_mut().set_byte(addr, value); + self.memory_bus.borrow_mut().write_byte(addr, value); self.registers.sp -= 1; } @@ -235,7 +236,7 @@ impl CpuInternals for Cpu { self.registers.sp += 1; let addr = STACK_ADDR | self.registers.sp as u16; - self.memory_bus.borrow().get_byte(addr) + self.memory_bus.borrow().read_byte(addr) } fn stack_pop_word(&mut self) -> u16 { diff --git a/core/src/cpu/op.rs b/core/src/cpu/op.rs index 279549a..bcc0d5e 100644 --- a/core/src/cpu/op.rs +++ b/core/src/cpu/op.rs @@ -29,6 +29,7 @@ use OperationType::{ LDA, LDX, LDY, LSR, NOP, ORA, PHA, PHP, PLA, PLP, RLA, ROL, ROR, RRA, RTI, RTS, SAX, SBC, SBX, SEC, SED, SEI, SHX, SHY, SLO, SRE, STA, STP, STX, STY, TAS, TAX, TAY, TSX, TXA, TXS, TYA, }; +use crate::MemoryBus; /// CPU Instruction. #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -76,7 +77,7 @@ impl Operand { OperandType::Immediate => self.value_u8(), OperandType::Address => { let addr = self.value; - cpu.memory_bus.borrow().get_byte(addr) + cpu.memory_bus.borrow().read_byte(addr) } } } @@ -86,7 +87,7 @@ impl Operand { OperandType::Accumulator => cpu.registers.a = value, OperandType::Address => { let addr = self.value; - cpu.memory_bus.borrow_mut().set_byte(addr, value); + cpu.memory_bus.borrow_mut().write_byte(addr, value); } OperandType::Immediate => unreachable!("Cannot write to immediate operand"), } @@ -226,8 +227,8 @@ impl Cpu { if ref_addr & 0xFF == 0xFF { // Error in NES CPU for JMP op - let low = self.memory_bus.borrow().get_byte(ref_addr) as u16; - let high = self.memory_bus.borrow().get_byte(ref_addr & 0xFF00) as u16; + let low = self.memory_bus.borrow().read_byte(ref_addr) as u16; + let high = self.memory_bus.borrow().read_byte(ref_addr & 0xFF00) as u16; low + (high << 8) } else { self.memory_bus.borrow().get_word(ref_addr) @@ -239,8 +240,8 @@ impl Cpu { let mut ref_addr = self.program_get_next_byte() as u16; ref_addr += self.registers.x as u16; - let low = self.memory_bus.borrow().get_byte(ref_addr & 0xFF) as u16; - let high = self.memory_bus.borrow().get_byte((ref_addr + 1) & 0xFF) as u16; + let low = self.memory_bus.borrow().read_byte(ref_addr & 0xFF) as u16; + let high = self.memory_bus.borrow().read_byte((ref_addr + 1) & 0xFF) as u16; low + (high << 8) } @@ -248,8 +249,8 @@ impl Cpu { fn operand_addr_indy(&mut self, page_crossed: &mut bool) -> u16 { let ref_addr = self.program_get_next_byte() as u16; - let low = self.memory_bus.borrow().get_byte(ref_addr) as u16; - let high = self.memory_bus.borrow().get_byte((ref_addr + 1) & 0xFF) as u16; + let low = self.memory_bus.borrow().read_byte(ref_addr) as u16; + let high = self.memory_bus.borrow().read_byte((ref_addr + 1) & 0xFF) as u16; let addr = low + (high << 8); let incr_addr = addr + self.registers.y as u16; diff --git a/core/src/lib.rs b/core/src/lib.rs index 2661ec5..ca0f622 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,11 +1,45 @@ +use std::cell::RefCell; +use std::rc::Weak; + mod cpu; mod mappers; -mod memory; +mod ppu; mod rom; pub mod system; -mod ppu; pub trait Clock { /// Run a clock cycle fn cycle(&mut self); -} \ No newline at end of file +} + +pub trait MemoryBus { + /// Gets a byte from the memory bus. + /// + /// # Arguments + /// * `addr` - The address of the byte + fn read_byte(&self, addr: u16) -> u8; + + /// Sets a byte in the memory bus + /// + /// # Arguments + /// * `addr` - The address of the byte + /// * `value` - The value to set + fn write_byte(&mut self, addr: u16, value: u8); +} + +pub trait Observable { + /// Registers an observer to be notified on changes. + /// + /// # Arguments + /// * observer - An observer implementation to register + fn register_observer(&mut self, observer: Weak>>); +} + +pub trait Observer { + /// Called when a value was updated in an observable. + /// + /// # Arguments + /// * key - The key of the value + /// * value - The updated value + fn notify_updated(&mut self, key: Key, value: Value); +} diff --git a/core/src/mappers/mod.rs b/core/src/mappers/mod.rs index 2a97ec2..3c81aac 100644 --- a/core/src/mappers/mod.rs +++ b/core/src/mappers/mod.rs @@ -1,5 +1,5 @@ use crate::mappers::nrom::NRom; -use crate::rom::Rom; +use crate::rom::{NametableMirroring, Rom}; mod nrom; @@ -15,6 +15,9 @@ pub trait Mapper { /// # Arguments /// * addr - The address of the byte to read fn get_chr_byte(&self, addr: u16) -> u8; + + /// Gets the orientation of the nametables. + fn are_nametables_mirrored(&self) -> NametableMirroring; } /// Gets a mapper for a ROM. @@ -28,4 +31,4 @@ pub fn get_mapper(rom: Rom) -> Box { }; Box::new(instance) -} \ No newline at end of file +} diff --git a/core/src/mappers/nrom.rs b/core/src/mappers/nrom.rs index 0599807..bdd7ad2 100644 --- a/core/src/mappers/nrom.rs +++ b/core/src/mappers/nrom.rs @@ -1,6 +1,6 @@ use simplelog::info; use crate::mappers::Mapper; -use crate::rom::Rom; +use crate::rom::{NametableMirroring, Rom}; const PRG_BANK_SIZE: usize = 0x4000; const CHR_BANK_SIZE: usize = 0x2000; @@ -36,4 +36,8 @@ impl Mapper for NRom { assert!(addr_usize < CHR_BANK_SIZE); self.rom.chr_rom[addr_usize] } + + fn are_nametables_mirrored(&self) -> NametableMirroring { + self.rom.nametable_mirroring + } } diff --git a/core/src/ppu/memory.rs b/core/src/ppu/memory.rs new file mode 100644 index 0000000..5b17a29 --- /dev/null +++ b/core/src/ppu/memory.rs @@ -0,0 +1,136 @@ +use crate::mappers::Mapper; +use crate::rom::NametableMirroring; +use crate::MemoryBus; +use simplelog::warn; + +const NAMETABLE_SIZE: usize = 0x400; +const PALETTE_TABLE_SIZE: usize = 0x20; +const VRAM_SIZE: usize = 0x4000; + +pub struct PpuMemoryBus { + /// The ROM mapper. Only present after a ROM has been "inserted" in the system. + mapper: Option>, + + /// The first nametable. + nametable_0: [u8; NAMETABLE_SIZE], + + /// The second nametable. + nametable_1: [u8; NAMETABLE_SIZE], + + /// The palette table. + palette: [u8; PALETTE_TABLE_SIZE], +} + +impl PpuMemoryBus { + pub fn new() -> Self { + Self { + mapper: None, + nametable_0: [0u8; NAMETABLE_SIZE], + nametable_1: [0u8; NAMETABLE_SIZE], + palette: [0u8; PALETTE_TABLE_SIZE], + } + } + + fn read_nametable(&self, addr: u16) -> u8 { + assert!(addr >= 0x2000); + assert!(addr < 0x3000); + + let nametable_addr = (addr - 0x2000) as usize; + let mirroring = match self.mapper.as_ref() { + None => NametableMirroring::Horizontal, + Some(mapper) => mapper.are_nametables_mirrored(), + }; + + if nametable_addr < NAMETABLE_SIZE { + self.nametable_0[nametable_addr] + } else if nametable_addr < NAMETABLE_SIZE * 2 { + if mirroring == NametableMirroring::Horizontal { + self.nametable_0[nametable_addr - NAMETABLE_SIZE] + } else { + self.nametable_1[nametable_addr - NAMETABLE_SIZE] + } + } else if nametable_addr < NAMETABLE_SIZE * 3 { + if mirroring == NametableMirroring::Horizontal { + self.nametable_1[nametable_addr - NAMETABLE_SIZE * 2] + } else { + self.nametable_0[nametable_addr - NAMETABLE_SIZE * 2] + } + } else { + self.nametable_1[nametable_addr - NAMETABLE_SIZE * 3] + } + } + + fn write_nametable(&mut self, addr: u16, data: u8) { + assert!(addr >= 0x2000); + assert!(addr < 0x3000); + + let nametable_addr = (addr - 0x2000) as usize; + let mirroring = match self.mapper.as_ref() { + None => NametableMirroring::Horizontal, + Some(mapper) => mapper.are_nametables_mirrored(), + }; + + let nametable = if nametable_addr < NAMETABLE_SIZE { + self.nametable_0.as_mut_slice() + } else if nametable_addr < NAMETABLE_SIZE * 2 { + if mirroring == NametableMirroring::Horizontal { + self.nametable_0.as_mut_slice() + } else { + self.nametable_1.as_mut_slice() + } + } else if nametable_addr < NAMETABLE_SIZE * 3 { + if mirroring == NametableMirroring::Horizontal { + self.nametable_1.as_mut_slice() + } else { + self.nametable_0.as_mut_slice() + } + } else { + self.nametable_1.as_mut_slice() + }; + + let nametable_addr = addr as usize % NAMETABLE_SIZE; + nametable[nametable_addr] = data; + } +} + +impl MemoryBus for PpuMemoryBus { + fn read_byte(&self, addr: u16) -> u8 { + assert!(addr < VRAM_SIZE as u16); + + if addr < 0x2000 { + if self.mapper.is_none() { + warn!( + "Tried to read PPU memory address {} before setting a mapper", + addr + ); + return 0; + } + + let mapper = self.mapper.as_ref().unwrap(); + mapper.get_chr_byte(addr) + } else if addr < 0x3000 { + self.read_nametable(addr) + } else if addr < 0x3F00 { + warn!("Read from unused PPU address space: {:#04x}", addr); + 0 + } else { + let palette_addr = (addr as usize - 0x3F00) % PALETTE_TABLE_SIZE; + self.palette[palette_addr] + } + } + + fn write_byte(&mut self, addr: u16, value: u8) { + assert!(addr < VRAM_SIZE as u16); + + if addr < 0x2000 { + warn!("Unsupported write to CHR ROM at address {:#04x}", addr); + } else if addr < 0x3000 { + self.write_nametable(addr, value); + } else if addr < 0x3F00 { + warn!("Write to unused PPU address space: {:#04x}", addr); + } else { + let palette_addr = (addr as usize - 0x3F00) % PALETTE_TABLE_SIZE; + self.palette[palette_addr] = value; + } + } +} diff --git a/core/src/ppu/mod.rs b/core/src/ppu/mod.rs index b16290d..db7fd79 100644 --- a/core/src/ppu/mod.rs +++ b/core/src/ppu/mod.rs @@ -1,26 +1,168 @@ -use crate::memory::{MemoryBus, MemoryObserver}; -use crate::Clock; -use std::cell::RefCell; -use std::rc::Rc; -use simplelog::info; +mod memory; +mod registers; + +use crate::ppu::memory::PpuMemoryBus; +use crate::ppu::registers::{PpuControl, PpuInternalRegisters, PpuRegisters, PpuStatus}; +use crate::ppu::registers::{ + PPU_REG_ADDR_ADDR, PPU_REG_ADDR_CTRL, PPU_REG_ADDR_DATA, PPU_REG_ADDR_MASK, + PPU_REG_ADDR_OAMADDR, PPU_REG_ADDR_OAMDATA, PPU_REG_ADDR_OAMDMA, PPU_REG_ADDR_SCROLL, + PPU_REG_ADDR_STATUS, +}; +use crate::{Clock, MemoryBus, Observer}; +use simplelog::{debug, info}; pub struct Ppu { - memory_bus: Rc>, + registers: PpuRegisters, + internal_registers: PpuInternalRegisters, + memory_bus: PpuMemoryBus, } impl Ppu { - pub fn new(memory_bus: Rc>) -> Self { - Self { memory_bus: memory_bus.clone() } + pub fn new() -> Self { + Self { + registers: PpuRegisters { + control: 0, + mask: 0, + status: 0, + oam_addr: 0, + oam_data: 0, + scroll: 0, + addr: 0, + data: 0, + oam_dma: 0, + }, + internal_registers: PpuInternalRegisters { + v: 0, + t: 0, + x: 0, + w: false, + }, + memory_bus: PpuMemoryBus::new(), + } } - - fn set_register(&self, addr: u16, value: u8) { - + + pub fn get_background_pattern_table_addr(&self) -> u16 { + let control = self.registers.control & PpuControl::BackgroundPatternTableAddress.bits(); + (control as u16) << 8 + } + + pub fn get_vram_addr_increment(&self) -> u16 { + if self.registers.control & PpuControl::VramAdressIncrement.bits() > 0 { + // Going down + 32 + } else { + // Going across + 1 + } + } + + pub fn read_register(&mut self, addr: u16) -> u8 { + match addr { + PPU_REG_ADDR_CTRL | PPU_REG_ADDR_MASK | PPU_REG_ADDR_OAMADDR | PPU_REG_ADDR_SCROLL + | PPU_REG_ADDR_ADDR | PPU_REG_ADDR_OAMDMA => { + assert!(false); + 0 + } + PPU_REG_ADDR_STATUS => { + self.internal_registers.w = false; + let status = self.registers.status; + + // VBlank is cleared on read + self.registers.status = !PpuStatus::VBlank.bits(); + + status + } + PPU_REG_ADDR_OAMDATA => self.registers.oam_data, + PPU_REG_ADDR_DATA => { + // Access to VRAM memory is slow, so reading it a first time generally return the memory at the previous address. + // So we get the data first, then update the register. + let mut data = self.registers.data; + self.registers.data = self.memory_bus.read_byte(self.internal_registers.v); + + if self.internal_registers.v > 0x3EFF { + // But the palette data is returned immediately + data = self.registers.data; + } + + self.internal_registers.v += self.get_vram_addr_increment(); + data + } + _ => unreachable!(), + } + } + + pub fn write_register(&mut self, addr: u16, data: u8) { + debug!("Write to PPU register: {:#04x} -> {:#02x}", addr, data); + + match addr { + PPU_REG_ADDR_CTRL => { + // TODO: Was broken, changed while loading palette + // ppu_state.temp_ppu_addr = (ppu_state.temp_ppu_addr & 0xf3ff) | ((data & PPU_CTRL_BASE_NAMETABLE_ADDR) << 10); + + self.registers.control = data; + + // TODO: Trigger NMI + } + PPU_REG_ADDR_MASK => { + self.registers.mask = data; + } + PPU_REG_ADDR_OAMADDR => { + self.registers.oam_addr = data; + } + PPU_REG_ADDR_OAMDATA => { + self.registers.oam_data = data; + } + PPU_REG_ADDR_SCROLL => { + // TODO: Understand and fix with a game using scrolling + if !self.internal_registers.w { + // Scroll X + self.internal_registers.t = + (self.internal_registers.t & 0xFFE0) | (data as u16 >> 3); + self.internal_registers.x = data & 0x7; + } else { + // Scroll Y + self.internal_registers.t &= 0x0C1F; + self.internal_registers.t |= (data as u16 & 0xF8) << 2; + self.internal_registers.t |= (data as u16 & 0x7) << 12; + // TODO: ppu_state.y_scroll = data; + } + + self.internal_registers.w = !self.internal_registers.w; + } + PPU_REG_ADDR_ADDR => { + if !self.internal_registers.w { + self.internal_registers.t = + (self.internal_registers.t & 0xFF) | (data as u16 & 0x3F) << 8; + } else { + self.internal_registers.t = (self.internal_registers.t & 0xFF00) | data as u16; + self.internal_registers.v = self.internal_registers.t; + } + + self.registers.addr = data; + self.internal_registers.w = !self.internal_registers.w; + } + PPU_REG_ADDR_DATA => { + self.registers.data = data; + let addr = self.internal_registers.v; + self.memory_bus.write_byte(addr, data); + self.internal_registers.v += self.get_vram_addr_increment(); + } + PPU_REG_ADDR_OAMDMA => { + self.registers.oam_dma = data; + } + PPU_REG_ADDR_STATUS => { + assert!(false); + } + _ => unreachable!(), + } } } -impl MemoryObserver for Ppu { - fn notify_mem_byte_updated(&mut self, addr: u16, value: u8) { - info!("MemChange: {:#04x} -> {:#02x}", addr, value); +impl Observer for Ppu { + fn notify_updated(&mut self, key: u16, value: u8) { + if (key >= PPU_REG_ADDR_CTRL && key < PPU_REG_ADDR_DATA) || key == PPU_REG_ADDR_OAMDMA { + self.write_register(key, value); + } } } diff --git a/core/src/ppu/registers.rs b/core/src/ppu/registers.rs new file mode 100644 index 0000000..6f5aa3d --- /dev/null +++ b/core/src/ppu/registers.rs @@ -0,0 +1,172 @@ +use bitflags::bitflags; + +pub const PPU_REG_ADDR_CTRL: u16 = 0x2000; +pub const PPU_REG_ADDR_MASK: u16 = 0x2001; +pub const PPU_REG_ADDR_STATUS: u16 = 0x2002; +pub const PPU_REG_ADDR_OAMADDR: u16 = 0x2003; +pub const PPU_REG_ADDR_OAMDATA: u16 = 0x2004; +pub const PPU_REG_ADDR_SCROLL: u16 = 0x2005; +pub const PPU_REG_ADDR_ADDR: u16 = 0x2006; +pub const PPU_REG_ADDR_DATA: u16 = 0x2007; +pub const PPU_REG_ADDR_OAMDMA: u16 = 0x4014; + +pub struct PpuRegisters { + /// PPUCTRL ($2000, W) - + /// NMI enable (V), PPU master/slave (P), sprite height (H), background tile select (B), sprite tile select (S), increment mode (I), nametable select / X and Y scroll bit 8 (NN) + // 7 bit 0 + // ---- ---- + // VPHB SINN + // |||| |||| + // |||| ||++- Base nametable address + // |||| || (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00) + // |||| |+--- VRAM address increment per CPU read/write of PPUDATA + // |||| | (0: add 1, going across; 1: add 32, going down) + // |||| +---- Sprite pattern table address for 8x8 sprites + // |||| (0: $0000; 1: $1000; ignored in 8x16 mode) + // |||+------ Background pattern table address (0: $0000; 1: $1000) + // ||+------- Sprite size (0: 8x8 pixels; 1: 8x16 pixels – see PPU OAM#Byte 1) + // |+-------- PPU master/slave select + // | (0: read backdrop from EXT pins; 1: output color on EXT pins) + // +--------- Vblank NMI enable (0: off, 1: on) + pub control: u8, + + /// PPUMASK ($2001, W) - + /// Color emphasis (BGR), sprite enable (s), background enable (b), sprite left column enable (M), background left column enable (m), greyscale (G) + // 7 bit 0 + // ---- ---- + // BGRs bMmG + // |||| |||| + // |||| |||+- Greyscale (0: normal color, 1: greyscale) + // |||| ||+-- 1: Show background in leftmost 8 pixels of screen, 0: Hide + // |||| |+--- 1: Show sprites in leftmost 8 pixels of screen, 0: Hide + // |||| +---- 1: Enable background rendering + // |||+------ 1: Enable sprite rendering + // ||+------- Emphasize red (green on PAL/Dendy) + // |+-------- Emphasize green (red on PAL/Dendy) + // +--------- Emphasize blue + pub mask: u8, + + /// PPUSTATUS ($2002, R) - + /// Vblank (V), sprite 0 hit (S), sprite overflow (O); read resets write pair for $2005/$2006 + // 7 bit 0 + // ---- ---- + // VSOx xxxx + // |||| |||| + // |||+-++++- (PPU open bus or 2C05 PPU identifier) + // ||+------- Sprite overflow flag + // |+-------- Sprite 0 hit flag + // +--------- Vblank flag, cleared on read. Unreliable; see below. + pub status: u8, + + /// OAMADDR ($2003, W) - OAM read/write address + // 7 bit 0 + // ---- ---- + // AAAA AAAA + // |||| |||| + // ++++-++++- OAM address + pub oam_addr: u8, + + /// OAMDATA ($2004, RW) - OAM data read/write + // 7 bit 0 + // ---- ---- + // DDDD DDDD + // |||| |||| + // ++++-++++- OAM data + pub oam_data: u8, + + /// PPUSCROLL ($2005, Wx2) - X and Y scroll bits 7-0 (two writes: X scroll, then Y scroll) + // 1st write + // 7 bit 0 + // ---- ---- + // XXXX XXXX + // |||| |||| + // ++++-++++- X scroll bits 7-0 (bit 8 in PPUCTRL bit 0) + // + // 2nd write + // 7 bit 0 + // ---- ---- + // YYYY YYYY + // |||| |||| + // ++++-++++- Y scroll bits 7-0 (bit 8 in PPUCTRL bit 1) + pub scroll: u8, + + /// PPUADDR ($2006, Wx2) - VRAM address (two writes: most significant byte, then least significant byte) + // 1st write 2nd write + // 15 bit 8 7 bit 0 + // ---- ---- ---- ---- + // ..AA AAAA AAAA AAAA + // || |||| |||| |||| + // ++-++++--++++-++++- VRAM address + pub addr: u8, + + /// PPUDATA ($2007, RW) - VRAM data read/write + // 7 bit 0 + // ---- ---- + // DDDD DDDD + // |||| |||| + // ++++-++++- VRAM data + pub data: u8, + + /// OAMDMA ($4014, W) - OAM DMA high address + // 7 bit 0 + // ---- ---- + // AAAA AAAA + // |||| |||| + // ++++-++++- Source page (high byte of source address) + pub oam_dma: u8 +} + +pub struct PpuInternalRegisters { + + /// During rendering, used for the scroll position. Outside of rendering, used as the current VRAM address. + pub v: u16, + + /// During rendering, specifies the starting coarse-x scroll for the next scanline and the starting y scroll for the screen. + /// Outside of rendering, holds the scroll or VRAM address before transferring it to v. + pub t: u16, + + /// The fine-x position of the current scroll, used during rendering alongside v. + pub x: u8, + + /// Toggles on each write to either PPUSCROLL or PPUADDR, indicating whether this is the first or second write. Clears on reads of PPUSTATUS. + /// Sometimes called the 'write latch' or 'write toggle'. + pub w: bool, +} + +bitflags! { + pub struct PpuControl: u8 { + const BaseNametableAddress = 0b0000_0011; + const VramAdressIncrement = 0b0000_0100; + const SpritePatternTableAddress = 0b0000_1000; + const BackgroundPatternTableAddress = 0b0001_0000; + const SpriteSize = 0b0010_0000; + const PpuMasterSlave = 0b0100_0000; + const VBlankNmiEnable = 0b1000_0000; + } +} + +bitflags! { + pub struct PpuMask: u8 { + const Greyscale = 0b0000_0001; + const ShowLeftBackground = 0b0000_0010; + const ShowLeftSprites = 0b0000_0100; + const EnableBackgroundRendering = 0b0000_1000; + const EnableSpriteRendering = 0b0001_0000; + const EmphasizeRed = 0b0010_0000; + const EmphasizeGreen = 0b0100_0000; + const EmphasizeBlue = 0b1000_0000; + } +} + +bitflags! { + pub struct PpuStatus: u8 { + const OpenBus = 0b0001_1111; + const SpriteOverflow = 0b0010_0000; + const Sprite0Hit = 0b0100_0000; + const VBlank = 0b1000_0000; + } +} + +impl PpuRegisters { + +} diff --git a/core/src/rom/mod.rs b/core/src/rom/mod.rs index 88c4416..11befac 100644 --- a/core/src/rom/mod.rs +++ b/core/src/rom/mod.rs @@ -44,11 +44,13 @@ pub struct Rom { pub sub_mapper: u8 } +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum NametableMirroring { Horizontal, Vertical, } +#[derive(Debug, Copy, Clone)] pub enum CpuTiming { Ntsc, Multiple, diff --git a/core/src/system.rs b/core/src/system.rs index 3b9c093..b34404e 100644 --- a/core/src/system.rs +++ b/core/src/system.rs @@ -3,9 +3,9 @@ use std::cell::RefCell; use std::rc::Rc; use crate::cpu::Cpu; use crate::mappers::get_mapper; -use crate::memory::MemoryBus; +use crate::cpu::memory::CpuMemoryBus; use crate::rom::{Rom, RomReadError}; -use crate::Clock; +use crate::{Clock, Observable}; use crate::ppu::Ppu; const CPU_CLOCK_DIVISOR: usize = 12; @@ -18,15 +18,15 @@ const REFRESH_RATE: usize = 60; // 60 Hz pub struct System { cpu: Cpu, - memory_bus: Rc>, + memory_bus: Rc>, ppu: Rc> } impl System { pub fn new() -> Self { - let memory_bus = Rc::new(RefCell::new(MemoryBus::new())); + let memory_bus = Rc::new(RefCell::new(CpuMemoryBus::new())); let cpu = Cpu::new(Rc::clone(&memory_bus)); - let ppu = Rc::new(RefCell::new(Ppu::new(Rc::clone(&memory_bus)))); + let ppu = Rc::new(RefCell::new(Ppu::new())); let weak_ppu = Rc::downgrade(&ppu); memory_bus.borrow_mut().register_observer(weak_ppu);