PPU registers and memory bus

This commit is contained in:
william 2025-02-01 17:02:40 -05:00
parent eb7e842a81
commit a1965d6a3c
12 changed files with 571 additions and 88 deletions

View File

@ -2,11 +2,12 @@ use crate::cpu::op::AddressingMode::*;
use crate::cpu::op::{AddressingMode, Instruction}; use crate::cpu::op::{AddressingMode, Instruction};
use crate::cpu::Cpu; use crate::cpu::Cpu;
use simplelog::{trace}; use simplelog::{trace};
use crate::MemoryBus;
impl Cpu { impl Cpu {
pub fn disassemble_next_instr(&self) { pub fn disassemble_next_instr(&self) {
let registers = self.registers; 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 instr: Instruction = Self::INSTRUCTIONS[op_code];
let operation = instr.op().to_string(); let operation = instr.op().to_string();

View File

@ -1,7 +1,8 @@
use std::cell::RefCell;
use crate::mappers::Mapper; use crate::mappers::Mapper;
use crate::{MemoryBus, Observable, Observer};
use simplelog::warn; use simplelog::warn;
use std::rc::{Weak}; use std::cell::RefCell;
use std::rc::Weak;
const MEMORY_MAX_ADDR: u16 = 0xFFFF; const MEMORY_MAX_ADDR: u16 = 0xFFFF;
const PPU_MAX_ADDR: u16 = 0x4000; const PPU_MAX_ADDR: u16 = 0x4000;
@ -11,14 +12,14 @@ const CARTRIDGE_RAM_MAX_ADDR: u16 = 0x8000;
const RAM_SIZE: usize = 0x0800; const RAM_SIZE: usize = 0x0800;
const RAM_MAX_ADDR: u16 = 0x2000; const RAM_MAX_ADDR: u16 = 0x2000;
pub struct MemoryBus { pub struct CpuMemoryBus {
mapper: Option<Box<dyn Mapper>>, mapper: Option<Box<dyn Mapper>>,
ram: [u8; RAM_SIZE], ram: [u8; RAM_SIZE],
observers: Vec<Weak<RefCell<dyn MemoryObserver>>>, observers: Vec<Weak<RefCell<dyn Observer<u16, u8>>>>,
} }
impl MemoryBus { impl CpuMemoryBus {
pub fn new() -> Self { pub fn new() -> Self {
let ram = [0; RAM_SIZE]; let ram = [0; RAM_SIZE];
Self { Self {
@ -33,11 +34,29 @@ impl MemoryBus {
self.mapper = Some(mapper); self.mapper = Some(mapper);
} }
/// Gets a byte from memory. /// Gets a word from memory.
/// ///
/// # Arguments /// # Arguments
/// * `addr` - The address of the byte /// * `addr` - The address of the word
pub fn get_byte(&self, addr: u16) -> u8 { 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); assert!(addr < MEMORY_MAX_ADDR);
if addr < RAM_MAX_ADDR { if addr < RAM_MAX_ADDR {
let ram_addr = (addr as usize) % RAM_SIZE; let ram_addr = (addr as usize) % RAM_SIZE;
@ -60,7 +79,7 @@ impl MemoryBus {
if self.mapper.is_none() { if self.mapper.is_none() {
warn!( warn!(
"Tried to read memory address {} before setting a mapper", "Tried to read CPU memory address {} before setting a mapper",
addr addr
); );
return 0; return 0;
@ -71,23 +90,7 @@ impl MemoryBus {
} }
} }
/// Gets a word from memory. fn write_byte(&mut self, addr: u16, value: u8) {
///
/// # 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) {
assert!(addr < MEMORY_MAX_ADDR); assert!(addr < MEMORY_MAX_ADDR);
if addr < RAM_MAX_ADDR { if addr < RAM_MAX_ADDR {
let ram_addr = (addr as usize) % RAM_SIZE; let ram_addr = (addr as usize) % RAM_SIZE;
@ -106,28 +109,12 @@ impl MemoryBus {
assert!(false); assert!(false);
} }
// Notify the observers self.notify_observers(addr, value);
for observer in self.observers.iter() {
match observer.upgrade() {
None => {}
Some(rc) => rc.borrow_mut().notify_mem_byte_updated(addr, value)
}
}
} }
}
pub fn register_observer(&mut self, observer: Weak<RefCell<dyn MemoryObserver>>) {
impl Observable<u16, u8> for CpuMemoryBus {
fn register_observer(&mut self, observer: Weak<RefCell<dyn Observer<u16, u8>>>) {
self.observers.push(observer); 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);
}

View File

@ -1,9 +1,10 @@
mod disassembler; mod disassembler;
mod op; mod op;
mod operations; mod operations;
pub mod memory;
use crate::memory::MemoryBus; use memory::CpuMemoryBus;
use crate::Clock; use crate::{Clock, MemoryBus};
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use bitflags::bitflags; use bitflags::bitflags;
@ -19,7 +20,7 @@ pub struct Cpu {
registers: CpuRegisters, registers: CpuRegisters,
/// The memory bus accessible by the CPU /// The memory bus accessible by the CPU
memory_bus: Rc<RefCell<MemoryBus>>, memory_bus: Rc<RefCell<CpuMemoryBus>>,
/// The number of cycles ran on the CPU /// The number of cycles ran on the CPU
cycle: usize, cycle: usize,
@ -137,7 +138,7 @@ pub trait CpuInternals {
} }
impl Cpu { impl Cpu {
pub fn new(memory_bus: Rc<RefCell<MemoryBus>>) -> Self { pub fn new(memory_bus: Rc<RefCell<CpuMemoryBus>>) -> Self {
Cpu { Cpu {
registers: CpuRegisters { registers: CpuRegisters {
pc: 0x8000, pc: 0x8000,
@ -190,7 +191,7 @@ impl CpuInternals for Cpu {
} }
fn program_get_next_byte(&mut self) -> u8 { 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; self.registers.pc += 1;
byte byte
@ -220,7 +221,7 @@ impl CpuInternals for Cpu {
assert!(self.registers.sp > 0); assert!(self.registers.sp > 0);
let addr = STACK_ADDR | self.registers.sp as u16; 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; self.registers.sp -= 1;
} }
@ -235,7 +236,7 @@ impl CpuInternals for Cpu {
self.registers.sp += 1; self.registers.sp += 1;
let addr = STACK_ADDR | self.registers.sp as u16; 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 { fn stack_pop_word(&mut self) -> u16 {

View File

@ -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, 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, SEC, SED, SEI, SHX, SHY, SLO, SRE, STA, STP, STX, STY, TAS, TAX, TAY, TSX, TXA, TXS, TYA,
}; };
use crate::MemoryBus;
/// CPU Instruction. /// CPU Instruction.
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -76,7 +77,7 @@ impl Operand {
OperandType::Immediate => self.value_u8(), OperandType::Immediate => self.value_u8(),
OperandType::Address => { OperandType::Address => {
let addr = self.value; 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::Accumulator => cpu.registers.a = value,
OperandType::Address => { OperandType::Address => {
let addr = self.value; 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"), OperandType::Immediate => unreachable!("Cannot write to immediate operand"),
} }
@ -226,8 +227,8 @@ impl Cpu {
if ref_addr & 0xFF == 0xFF { if ref_addr & 0xFF == 0xFF {
// Error in NES CPU for JMP op // Error in NES CPU for JMP op
let low = self.memory_bus.borrow().get_byte(ref_addr) as u16; let low = self.memory_bus.borrow().read_byte(ref_addr) as u16;
let high = self.memory_bus.borrow().get_byte(ref_addr & 0xFF00) as u16; let high = self.memory_bus.borrow().read_byte(ref_addr & 0xFF00) as u16;
low + (high << 8) low + (high << 8)
} else { } else {
self.memory_bus.borrow().get_word(ref_addr) 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; let mut ref_addr = self.program_get_next_byte() as u16;
ref_addr += self.registers.x as u16; ref_addr += self.registers.x as u16;
let low = self.memory_bus.borrow().get_byte(ref_addr & 0xFF) as u16; let low = self.memory_bus.borrow().read_byte(ref_addr & 0xFF) as u16;
let high = self.memory_bus.borrow().get_byte((ref_addr + 1) & 0xFF) as u16; let high = self.memory_bus.borrow().read_byte((ref_addr + 1) & 0xFF) as u16;
low + (high << 8) low + (high << 8)
} }
@ -248,8 +249,8 @@ impl Cpu {
fn operand_addr_indy(&mut self, page_crossed: &mut bool) -> u16 { fn operand_addr_indy(&mut self, page_crossed: &mut bool) -> u16 {
let ref_addr = self.program_get_next_byte() as u16; let ref_addr = self.program_get_next_byte() as u16;
let low = self.memory_bus.borrow().get_byte(ref_addr) as u16; let low = self.memory_bus.borrow().read_byte(ref_addr) as u16;
let high = self.memory_bus.borrow().get_byte((ref_addr + 1) & 0xFF) as u16; let high = self.memory_bus.borrow().read_byte((ref_addr + 1) & 0xFF) as u16;
let addr = low + (high << 8); let addr = low + (high << 8);
let incr_addr = addr + self.registers.y as u16; let incr_addr = addr + self.registers.y as u16;

View File

@ -1,11 +1,45 @@
use std::cell::RefCell;
use std::rc::Weak;
mod cpu; mod cpu;
mod mappers; mod mappers;
mod memory; mod ppu;
mod rom; mod rom;
pub mod system; pub mod system;
mod ppu;
pub trait Clock { pub trait Clock {
/// Run a clock cycle /// Run a clock cycle
fn cycle(&mut self); fn cycle(&mut self);
} }
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<Key, Value> {
/// Registers an observer to be notified on changes.
///
/// # Arguments
/// * observer - An observer implementation to register
fn register_observer(&mut self, observer: Weak<RefCell<dyn Observer<Key, Value>>>);
}
pub trait Observer<Key, Value> {
/// 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);
}

View File

@ -1,5 +1,5 @@
use crate::mappers::nrom::NRom; use crate::mappers::nrom::NRom;
use crate::rom::Rom; use crate::rom::{NametableMirroring, Rom};
mod nrom; mod nrom;
@ -15,6 +15,9 @@ pub trait Mapper {
/// # Arguments /// # Arguments
/// * addr - The address of the byte to read /// * addr - The address of the byte to read
fn get_chr_byte(&self, addr: u16) -> u8; 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. /// Gets a mapper for a ROM.
@ -28,4 +31,4 @@ pub fn get_mapper(rom: Rom) -> Box<dyn Mapper> {
}; };
Box::new(instance) Box::new(instance)
} }

View File

@ -1,6 +1,6 @@
use simplelog::info; use simplelog::info;
use crate::mappers::Mapper; use crate::mappers::Mapper;
use crate::rom::Rom; use crate::rom::{NametableMirroring, Rom};
const PRG_BANK_SIZE: usize = 0x4000; const PRG_BANK_SIZE: usize = 0x4000;
const CHR_BANK_SIZE: usize = 0x2000; const CHR_BANK_SIZE: usize = 0x2000;
@ -36,4 +36,8 @@ impl Mapper for NRom {
assert!(addr_usize < CHR_BANK_SIZE); assert!(addr_usize < CHR_BANK_SIZE);
self.rom.chr_rom[addr_usize] self.rom.chr_rom[addr_usize]
} }
fn are_nametables_mirrored(&self) -> NametableMirroring {
self.rom.nametable_mirroring
}
} }

136
core/src/ppu/memory.rs Normal file
View File

@ -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<Box<dyn Mapper>>,
/// 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;
}
}
}

View File

@ -1,26 +1,168 @@
use crate::memory::{MemoryBus, MemoryObserver}; mod memory;
use crate::Clock; mod registers;
use std::cell::RefCell;
use std::rc::Rc; use crate::ppu::memory::PpuMemoryBus;
use simplelog::info; 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 { pub struct Ppu {
memory_bus: Rc<RefCell<MemoryBus>>, registers: PpuRegisters,
internal_registers: PpuInternalRegisters,
memory_bus: PpuMemoryBus,
} }
impl Ppu { impl Ppu {
pub fn new(memory_bus: Rc<RefCell<MemoryBus>>) -> Self { pub fn new() -> Self {
Self { memory_bus: memory_bus.clone() } 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 { impl Observer<u16, u8> for Ppu {
fn notify_mem_byte_updated(&mut self, addr: u16, value: u8) { fn notify_updated(&mut self, key: u16, value: u8) {
info!("MemChange: {:#04x} -> {:#02x}", addr, value); if (key >= PPU_REG_ADDR_CTRL && key < PPU_REG_ADDR_DATA) || key == PPU_REG_ADDR_OAMDMA {
self.write_register(key, value);
}
} }
} }

172
core/src/ppu/registers.rs Normal file
View File

@ -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 {
}

View File

@ -44,11 +44,13 @@ pub struct Rom {
pub sub_mapper: u8 pub sub_mapper: u8
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum NametableMirroring { pub enum NametableMirroring {
Horizontal, Horizontal,
Vertical, Vertical,
} }
#[derive(Debug, Copy, Clone)]
pub enum CpuTiming { pub enum CpuTiming {
Ntsc, Ntsc,
Multiple, Multiple,

View File

@ -3,9 +3,9 @@ use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use crate::cpu::Cpu; use crate::cpu::Cpu;
use crate::mappers::get_mapper; use crate::mappers::get_mapper;
use crate::memory::MemoryBus; use crate::cpu::memory::CpuMemoryBus;
use crate::rom::{Rom, RomReadError}; use crate::rom::{Rom, RomReadError};
use crate::Clock; use crate::{Clock, Observable};
use crate::ppu::Ppu; use crate::ppu::Ppu;
const CPU_CLOCK_DIVISOR: usize = 12; const CPU_CLOCK_DIVISOR: usize = 12;
@ -18,15 +18,15 @@ const REFRESH_RATE: usize = 60; // 60 Hz
pub struct System { pub struct System {
cpu: Cpu, cpu: Cpu,
memory_bus: Rc<RefCell<MemoryBus>>, memory_bus: Rc<RefCell<CpuMemoryBus>>,
ppu: Rc<RefCell<Ppu>> ppu: Rc<RefCell<Ppu>>
} }
impl System { impl System {
pub fn new() -> Self { 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 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); let weak_ppu = Rc::downgrade(&ppu);
memory_bus.borrow_mut().register_observer(weak_ppu); memory_bus.borrow_mut().register_observer(weak_ppu);