PPU registers and memory bus
This commit is contained in:
parent
eb7e842a81
commit
a1965d6a3c
@ -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();
|
||||
|
@ -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<Box<dyn Mapper>>,
|
||||
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 {
|
||||
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<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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
@ -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<RefCell<MemoryBus>>,
|
||||
memory_bus: Rc<RefCell<CpuMemoryBus>>,
|
||||
|
||||
/// 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<RefCell<MemoryBus>>) -> Self {
|
||||
pub fn new(memory_bus: Rc<RefCell<CpuMemoryBus>>) -> 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 {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
136
core/src/ppu/memory.rs
Normal file
136
core/src/ppu/memory.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<RefCell<MemoryBus>>,
|
||||
registers: PpuRegisters,
|
||||
internal_registers: PpuInternalRegisters,
|
||||
memory_bus: PpuMemoryBus,
|
||||
}
|
||||
|
||||
impl Ppu {
|
||||
pub fn new(memory_bus: Rc<RefCell<MemoryBus>>) -> Self {
|
||||
Self { memory_bus: memory_bus.clone() }
|
||||
}
|
||||
|
||||
fn set_register(&self, addr: u16, value: u8) {
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryObserver for Ppu {
|
||||
fn notify_mem_byte_updated(&mut self, addr: u16, value: u8) {
|
||||
info!("MemChange: {:#04x} -> {:#02x}", addr, value);
|
||||
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 Observer<u16, u8> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
172
core/src/ppu/registers.rs
Normal file
172
core/src/ppu/registers.rs
Normal 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 {
|
||||
|
||||
}
|
@ -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,
|
||||
|
@ -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<RefCell<MemoryBus>>,
|
||||
memory_bus: Rc<RefCell<CpuMemoryBus>>,
|
||||
ppu: Rc<RefCell<Ppu>>
|
||||
}
|
||||
|
||||
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);
|
||||
|
Loading…
Reference in New Issue
Block a user