ppu bus
This commit is contained in:
@@ -56,7 +56,7 @@ fn main() -> Result<(), Error> {
|
||||
}
|
||||
|
||||
// disassemble instructions
|
||||
let disasm = Disasm::disassemble(&nes.memory, 0xC000, 0xFFFF).unwrap();
|
||||
let disasm = Disasm::disassemble(&nes.bus, 0xC000, 0xFFFF).unwrap();
|
||||
|
||||
// Prepare window and drawing resources
|
||||
|
||||
@@ -340,7 +340,7 @@ fn render_memory(glyphs: &mut GlyphBrush<Resources, Factory>, nes: &NES, offset:
|
||||
position_y.1 += FT_LINE_DISTANCE + FT_SIZE_PX;
|
||||
let mut line = format!("{:#06x}:", page);
|
||||
(0u16..16u16).map(|offset| offset + page)
|
||||
.map(|addr| nes.memory.readb(addr))
|
||||
.map(|addr| nes.bus.readb(addr))
|
||||
.map(|val| format!(" {:02x}", val))
|
||||
.for_each(|s| line.push_str(&s));
|
||||
glyphs.queue(Section {
|
||||
@@ -357,7 +357,7 @@ fn render_memory(glyphs: &mut GlyphBrush<Resources, Factory>, nes: &NES, offset:
|
||||
position_y.1 += FT_LINE_DISTANCE + FT_SIZE_PX;
|
||||
let mut line = format!("{:#06x}:", page);
|
||||
(0u16..16u16).map(|offset| offset + page)
|
||||
.map(|addr| nes.memory.readb(addr))
|
||||
.map(|addr| nes.bus.readb(addr))
|
||||
.map(|val| format!(" {:02x}", val))
|
||||
.for_each(|s| line.push_str(&s));
|
||||
glyphs.queue(Section {
|
||||
|
||||
32
src/nes.rs
32
src/nes.rs
@@ -19,42 +19,48 @@ pub mod ppu;
|
||||
// as the mediator between the different components and hold the RAM
|
||||
pub struct NES {
|
||||
pub cpu: CPU,
|
||||
pub bus: Bus,
|
||||
pub ppu: Rc<RefCell<PPU>>,
|
||||
pub memory: NESMemory,
|
||||
pub ppu_bus: PPUBus,
|
||||
pub clock_count: u64,
|
||||
}
|
||||
|
||||
impl NES {
|
||||
pub fn new() -> Self {
|
||||
// The memory module needs access to some parts of the nes like the ppu
|
||||
// to forward cpu reads to it. Because the NES also requires access
|
||||
// to the PPU for clocking, we use shared ownership. Otherwise, all
|
||||
// components except the CPU would have to live inside the memory
|
||||
// instance, which is conceptually not what we are looking for.
|
||||
// The bus module needs access to the ppu to forward cpu reads to it.
|
||||
// Because the NES also requires access to the PPU for clocking,
|
||||
// shared ownership is required. Otherwise, all
|
||||
// components except the CPU would have to live inside the bus
|
||||
// instance, which is conceptually not nice.
|
||||
let ppu = Rc::new(RefCell::new(PPU::new()));
|
||||
let ppu_bus = Rc::new(RefCell::new(PPUBus::new()));
|
||||
NES {
|
||||
cpu: CPU::new(),
|
||||
bus: Bus::new(ppu.clone(), ppu_bus.clone()),
|
||||
ppu: ppu.clone(),
|
||||
memory: NESMemory::new(ppu.clone()),
|
||||
ppu_bus: PPUBus::new(),
|
||||
clock_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Insert a cartridge into the NES. This inserts the cartridge memory
|
||||
// Insert a cartridge into the NES. This inserts the cartridge bus
|
||||
// into the NES address range
|
||||
pub fn insert_cartridge(&mut self, cartridge: Cartridge) {
|
||||
self.memory.insert_cartridge(cartridge);
|
||||
// both buses need to be connected to the cartridge
|
||||
let cart = Rc::new(RefCell::new(cartridge));
|
||||
self.bus.insert_cartridge(cart.clone());
|
||||
self.ppu_bus.insert_cartridge(cart.clone())
|
||||
}
|
||||
|
||||
// Initializes the NES CPU programm pointer
|
||||
pub fn start(&mut self) {
|
||||
self.cpu.find_pc_addr(&self.memory);
|
||||
self.cpu.find_pc_addr(&self.bus);
|
||||
}
|
||||
|
||||
// Reset the CPU
|
||||
pub fn reset(&mut self) {
|
||||
self.clock_count = 0;
|
||||
self.cpu.reset(&self.memory);
|
||||
self.cpu.reset(&self.bus);
|
||||
self.ppu.borrow_mut().reset();
|
||||
}
|
||||
|
||||
@@ -62,9 +68,9 @@ impl NES {
|
||||
pub fn clock(&mut self) {
|
||||
self.clock_count += 1;
|
||||
if self.clock_count % 3 == 0 {
|
||||
self.cpu.clock(&mut self.memory);
|
||||
self.cpu.clock(&mut self.bus);
|
||||
}
|
||||
self.ppu.borrow_mut().clock(&mut self.memory);
|
||||
self.ppu.borrow_mut().clock(&mut self.ppu_bus);
|
||||
if self.clock_count % 100000 == 0 {
|
||||
info!("clock {}", self.clock_count);
|
||||
}
|
||||
|
||||
@@ -12,22 +12,24 @@ pub const PPU_PHYS_RANGE: [Addr; 2] = [0x2000, 0x2007];
|
||||
pub const CART_ADDR_RANGE: [Addr; 2] = [0x4020, 0xffff];
|
||||
|
||||
// NES memory: Contains data from RAM, cartridge...
|
||||
pub struct NESMemory {
|
||||
pub struct Bus {
|
||||
ram: [Byte; RAM_SIZE], // 2kb
|
||||
cartridge: Option<Cartridge>,
|
||||
cartridge: Option<Rc<RefCell<Cartridge>>>,
|
||||
ppu: Rc<RefCell<PPU>>,
|
||||
ppu_bus: Rc<RefCell<PPUBus>>,
|
||||
}
|
||||
|
||||
impl NESMemory {
|
||||
pub fn new(ppu: Rc<RefCell<PPU>>) -> Self {
|
||||
NESMemory {
|
||||
impl Bus {
|
||||
pub fn new(ppu: Rc<RefCell<PPU>>, ppu_bus: Rc<RefCell<PPUBus>>) -> Self {
|
||||
Bus {
|
||||
ram: [0; RAM_SIZE],
|
||||
cartridge: None,
|
||||
ppu: ppu,
|
||||
ppu_bus: ppu_bus,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_cartridge(&mut self, c: Cartridge) {
|
||||
pub fn insert_cartridge(&mut self, c: Rc<RefCell<Cartridge>>) {
|
||||
self.cartridge = Some(c);
|
||||
}
|
||||
}
|
||||
@@ -46,11 +48,11 @@ pub trait Memory {
|
||||
}
|
||||
}
|
||||
|
||||
impl Memory for NESMemory {
|
||||
impl Memory for Bus {
|
||||
fn readb(&self, addr: Addr) -> Byte {
|
||||
if let Some(cartridge) = &self.cartridge {
|
||||
if CART_ADDR_RANGE[0] <= addr && addr <= CART_ADDR_RANGE[1] {
|
||||
return cartridge.readb(addr)
|
||||
return cartridge.borrow().readb(addr)
|
||||
}
|
||||
}
|
||||
if RAM_ADDR_RANGE[0] <= addr && addr <= RAM_ADDR_RANGE[1] {
|
||||
@@ -59,7 +61,8 @@ impl Memory for NESMemory {
|
||||
}
|
||||
if PPU_ADDR_RANGE[0] <= addr && addr <= PPU_ADDR_RANGE[1] {
|
||||
let mut ppu = self.ppu.borrow_mut();
|
||||
return ppu.readb(addr & PPU_PHYS_RANGE[1]);
|
||||
let ppu_bus = self.ppu_bus.borrow();
|
||||
return ppu.readb(&*ppu_bus, addr & PPU_PHYS_RANGE[1]);
|
||||
}
|
||||
0x0000 // generic response
|
||||
}
|
||||
@@ -67,7 +70,7 @@ impl Memory for NESMemory {
|
||||
fn writeb(&mut self, addr: Addr, data: Byte) {
|
||||
if let Some(cartridge) = &mut self.cartridge {
|
||||
if CART_ADDR_RANGE[0] <= addr && addr <= CART_ADDR_RANGE[1] {
|
||||
cartridge.writeb(addr, data)
|
||||
cartridge.borrow_mut().writeb(addr, data)
|
||||
}
|
||||
}
|
||||
if RAM_ADDR_RANGE[0] <= addr && addr <= RAM_ADDR_RANGE[1] {
|
||||
@@ -76,20 +79,12 @@ impl Memory for NESMemory {
|
||||
}
|
||||
if PPU_ADDR_RANGE[0] <= addr && addr <= PPU_ADDR_RANGE[1] {
|
||||
let mut ppu = self.ppu.borrow_mut();
|
||||
ppu.writeb(addr & PPU_PHYS_RANGE[1], data);
|
||||
let mut ppu_bus = self.ppu_bus.borrow_mut();
|
||||
ppu.writeb(&mut *ppu_bus, addr & PPU_PHYS_RANGE[1], data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl PPUMemory for NESMemory {
|
||||
// fn readb_ppu(&self, addr: Addr) -> Byte {
|
||||
|
||||
// }
|
||||
// fn writeb_ppu(&mut self, addr: Addr, data: Byte) {
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
pub trait MemoryReader {
|
||||
fn readb<T: Memory>(&self, mem: &T, addr: Addr) -> Byte {
|
||||
mem.readb(addr)
|
||||
@@ -104,18 +99,316 @@ pub trait MemoryReader {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub const PATTERN_MEMORY_SIZE: usize = 4096;
|
||||
pub const PATTERN_ADDR_RANGE: [Addr; 2] = [0x000, 0x1FFF];
|
||||
pub const NAMETABLE_MEMORY_SIZE: usize = 1024;
|
||||
pub const NAMETABLE_ADDR_RANGE: [Addr; 2] = [0x2000, 0x3EFF];
|
||||
pub const PALETTE_MEMORY_SIZE: usize = 32;
|
||||
pub const PALETTE_ADDR_RANGE: [Addr; 2] = [0x3F00, 0x3FFF];
|
||||
|
||||
pub struct PPUBus {
|
||||
pattern_memory: [[Byte; PATTERN_MEMORY_SIZE]; 2], // 8kb pattern memory
|
||||
nametable_memory: [[Byte; NAMETABLE_MEMORY_SIZE] ;4], // 2kb nametables
|
||||
palette_memory: [Byte; PALETTE_MEMORY_SIZE], // palettes
|
||||
cartridge: Option<Rc<RefCell<Cartridge>>>
|
||||
}
|
||||
|
||||
impl PPUBus {
|
||||
pub fn new() -> Self {
|
||||
PPUBus {
|
||||
pattern_memory: [[0; PATTERN_MEMORY_SIZE]; 2],
|
||||
nametable_memory: [[0; NAMETABLE_MEMORY_SIZE] ;4],
|
||||
palette_memory: [0; PALETTE_MEMORY_SIZE],
|
||||
cartridge: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_cartridge(&mut self, c: Rc<RefCell<Cartridge>>) {
|
||||
self.cartridge = Some(c);
|
||||
}
|
||||
}
|
||||
|
||||
impl PPUMemory for PPUBus {
|
||||
fn readb_ppu(&self, addr: Addr) -> Byte {
|
||||
if PATTERN_ADDR_RANGE[0] <= addr && addr <= PATTERN_ADDR_RANGE[1] {
|
||||
let table = if addr < 0x1000 { 0 } else { 1 };
|
||||
return self.pattern_memory[table as usize][(addr % 0x1000) as usize]
|
||||
}
|
||||
if NAMETABLE_ADDR_RANGE[0] <= addr && addr <= NAMETABLE_ADDR_RANGE[1] {
|
||||
let table = if addr < 0x2400 {
|
||||
0
|
||||
} else if addr < 0x2800 {
|
||||
1
|
||||
} else if addr < 0x2C00 {
|
||||
2
|
||||
} else {
|
||||
3
|
||||
};
|
||||
let rel_addr = addr - 0x2000;
|
||||
return self.nametable_memory[table][(rel_addr % 0x400) as usize]
|
||||
}
|
||||
if PALETTE_ADDR_RANGE[0] <= addr && addr <= PALETTE_ADDR_RANGE[1] {
|
||||
let rel_addr = addr - 0x3F00;
|
||||
return self.palette_memory[(rel_addr % 0x0020) as usize]
|
||||
}
|
||||
|
||||
0x00
|
||||
}
|
||||
fn writeb_ppu(&mut self, addr: Addr, data: Byte) {
|
||||
if PATTERN_ADDR_RANGE[0] <= addr && addr <= PATTERN_ADDR_RANGE[1] {
|
||||
let table = if addr < 0x1000 { 0 } else { 1 };
|
||||
self.pattern_memory[table as usize][(addr % 0x1000) as usize] = data;
|
||||
}
|
||||
if NAMETABLE_ADDR_RANGE[0] <= addr && addr <= NAMETABLE_ADDR_RANGE[1] {
|
||||
let table = if addr < 0x2400 {
|
||||
0
|
||||
} else if addr < 0x2800 {
|
||||
1
|
||||
} else if addr < 0x2C00 {
|
||||
2
|
||||
} else {
|
||||
3
|
||||
};
|
||||
let rel_addr = addr - 0x2000;
|
||||
self.nametable_memory[table][(rel_addr % 0x400) as usize] = data;
|
||||
}
|
||||
if PALETTE_ADDR_RANGE[0] <= addr && addr <= PALETTE_ADDR_RANGE[1] {
|
||||
let rel_addr = addr - 0x3F00;
|
||||
self.palette_memory[(rel_addr % 0x0020) as usize] = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PPU interface to allow read/write of memory
|
||||
pub trait PPUMemory {
|
||||
fn readb_ppu(&self, addr: Addr) -> Byte;
|
||||
fn writeb_ppu(&mut self, addr: Addr, data: Byte);
|
||||
}
|
||||
|
||||
// pub trait PPUMemoryReader {
|
||||
// fn readb_ppu<T: PPUMemory>(&self, mem: &T, addr: Addr) -> Byte {
|
||||
// mem.readb_ppu(addr)
|
||||
// }
|
||||
pub trait PPUMemoryReader {
|
||||
fn readb_ppu<T: PPUMemory>(&self, mem: &T, addr: Addr) -> Byte {
|
||||
mem.readb_ppu(addr)
|
||||
}
|
||||
|
||||
// fn writeb_ppu<T: PPUMemory>(&mut self, mem: &mut T, addr: Addr, data: Byte) {
|
||||
// mem.writeb_ppu(addr, data)
|
||||
// }
|
||||
// }
|
||||
fn writeb_ppu<T: PPUMemory>(&mut self, mem: &mut T, addr: Addr, data: Byte) {
|
||||
mem.writeb_ppu(addr, data)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_ppu_memory_pattern() {
|
||||
let mut mem = PPUBus::new();
|
||||
|
||||
// table/position 0,0
|
||||
assert_eq!(mem.readb_ppu(0x0), 0);
|
||||
mem.writeb_ppu(0x0, 100);
|
||||
assert_eq!(mem.readb_ppu(0x0), 100);
|
||||
|
||||
// table/position 0,1
|
||||
assert_eq!(mem.readb_ppu(0x1), 0);
|
||||
mem.writeb_ppu(0x1, 101);
|
||||
assert_eq!(mem.readb_ppu(0x1), 101);
|
||||
|
||||
// table/position 0,1000
|
||||
assert_eq!(mem.readb_ppu(0x0FFF), 0);
|
||||
mem.writeb_ppu(0x0FFF, 102);
|
||||
assert_eq!(mem.readb_ppu(0x0FFF), 102);
|
||||
|
||||
// table/position 1,0
|
||||
assert_eq!(mem.readb_ppu(0x1000), 0);
|
||||
mem.writeb_ppu(0x1000, 103);
|
||||
assert_eq!(mem.readb_ppu(0x1000), 103);
|
||||
|
||||
// table/position 1,1
|
||||
assert_eq!(mem.readb_ppu(0x1001), 0);
|
||||
mem.writeb_ppu(0x1001, 104);
|
||||
assert_eq!(mem.readb_ppu(0x1001), 104);
|
||||
|
||||
// table/position 1,1000
|
||||
assert_eq!(mem.readb_ppu(0x1FFF), 0);
|
||||
mem.writeb_ppu(0x1FFF, 105);
|
||||
assert_eq!(mem.readb_ppu(0x1FFF), 105);
|
||||
|
||||
// not pattern
|
||||
assert_eq!(mem.readb_ppu(0x2000), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ppu_memory_pattern_no_overwrite() {
|
||||
let mut mem = PPUBus::new();
|
||||
|
||||
// fill pattern
|
||||
for addr in 0x0 .. 0x1FFF + 1 {
|
||||
mem.writeb_ppu(addr, 0x1);
|
||||
}
|
||||
|
||||
// fill remaining addr space
|
||||
for addr in 0x2000 .. 0x3EFF + 1 {
|
||||
mem.writeb_ppu(addr, 0x2);
|
||||
}
|
||||
for addr in 0x3F00 .. 0x3FFF + 1 {
|
||||
mem.writeb_ppu(addr, 0x3);
|
||||
}
|
||||
|
||||
// pattern should not have changed
|
||||
for addr in 0x0 .. 0x1FFF + 1 {
|
||||
assert_eq!(0x1, mem.readb_ppu(addr));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ppu_memory_nametable_rw() {
|
||||
let mut mem = PPUBus::new();
|
||||
|
||||
// read/write something to nametable memory
|
||||
for (idx, addr) in (0x2000 .. 0x2FFF + 1).enumerate() {
|
||||
assert_eq!(0, mem.readb_ppu(addr));
|
||||
mem.writeb_ppu(addr, idx as Byte);
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
|
||||
// assert nametables are initialized correctly
|
||||
for (idx, addr) in (0x2000 .. 0x2FFF + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ppu_memory_nametable_not_overwritten() {
|
||||
let mut mem = PPUBus::new();
|
||||
|
||||
// write some value to whole nametable space
|
||||
for addr in 0x2000 .. 0x3EFF + 1 {
|
||||
mem.writeb_ppu(addr, 0x1);
|
||||
}
|
||||
// write something else to the remaining address space
|
||||
for addr in 0x0 .. 0x1FFF + 1 {
|
||||
mem.writeb_ppu(addr, 0x2);
|
||||
}
|
||||
for addr in 0x3F00 .. 0x3FFF + 1 {
|
||||
mem.writeb_ppu(addr, 0x3);
|
||||
}
|
||||
|
||||
// Namestables should not have changed
|
||||
for addr in 0x2000 .. 0x3EFF + 1 {
|
||||
assert_eq!(0x1, mem.readb_ppu(addr),
|
||||
"Nametable changed unxexpected at position {:#08x}", addr);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ppu_memory_nametable_mirroring() {
|
||||
let mut mem = PPUBus::new();
|
||||
|
||||
// read/write something to nametable memory
|
||||
for (idx, addr) in (0x2000 .. 0x2FFF + 1).enumerate() {
|
||||
mem.writeb_ppu(addr, idx as Byte);
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
|
||||
// mirror memory should have the same data
|
||||
for (idx, addr) in (0x3000 .. 0x3EFF + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
|
||||
// write data to mirrored addr range
|
||||
for (idx, addr) in (0x3000 .. 0x3EFF + 1).enumerate() {
|
||||
mem.writeb_ppu(addr, idx as Byte);
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
|
||||
// start memory should have the same data
|
||||
for (idx, addr) in (0x2000 .. 0x2FFF + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ppu_memory_palette_rw() {
|
||||
let mut mem = PPUBus::new();
|
||||
|
||||
// read/write something to palette memory
|
||||
for (idx, addr) in (0x3F00 .. 0x3F1F + 1).enumerate() {
|
||||
assert_eq!(0, mem.readb_ppu(addr));
|
||||
mem.writeb_ppu(addr, idx as Byte);
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
|
||||
// assert palette are initialized correctly
|
||||
for (idx, addr) in (0x3F00 .. 0x3F1F + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ppu_memory_palette_not_overwritten() {
|
||||
let mut mem = PPUBus::new();
|
||||
|
||||
// write some value to whole palette space
|
||||
for addr in 0x3F00 .. 0x3F1F + 1 {
|
||||
mem.writeb_ppu(addr, 0x1);
|
||||
}
|
||||
// write something else to the remaining address space
|
||||
for addr in 0x0 .. 0x1FFF + 1 {
|
||||
mem.writeb_ppu(addr, 0x2);
|
||||
}
|
||||
for addr in 0x2000 .. 0x3EFF + 1 {
|
||||
mem.writeb_ppu(addr, 0x3);
|
||||
}
|
||||
|
||||
// palette should not have changed
|
||||
for addr in 0x3F00 .. 0x3F1F + 1 {
|
||||
assert_eq!(0x1, mem.readb_ppu(addr),
|
||||
"Palette changed unxexpected at position {:#08x}", addr);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ppu_memory_palette_mirroring() {
|
||||
let mut mem = PPUBus::new();
|
||||
|
||||
// read/write something to palette memory
|
||||
for (idx, addr) in (0x3F00 .. 0x3F1F + 1).enumerate() {
|
||||
mem.writeb_ppu(addr, idx as Byte);
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
|
||||
// mirror memory should have the same data
|
||||
for (idx, addr) in (0x3F20 .. 0x3F3F + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
for (idx, addr) in (0x3F40 .. 0x3F5F + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
for (idx, addr) in (0x3F60 .. 0x3F7F + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
for (idx, addr) in (0x3F80 .. 0x3F9F + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
for (idx, addr) in (0x3FA0 .. 0x3FBF + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
for (idx, addr) in (0x3FC0 .. 0x3FDF + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
for (idx, addr) in (0x3FE0 .. 0x3FFF + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
|
||||
// write data to mirrored addr range
|
||||
for (idx, addr) in (0x3FC0 .. 0x3FDF + 1).enumerate() {
|
||||
mem.writeb_ppu(addr, idx as Byte + 1);
|
||||
}
|
||||
|
||||
// start memory should have the same data
|
||||
for (idx, addr) in (0x3F00 .. 0x3F1F + 1).enumerate() {
|
||||
assert_eq!(idx as Byte + 1, mem.readb_ppu(addr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::nes::memory::{Memory,PPUMemory};
|
||||
use crate::nes::memory::PPUMemory;
|
||||
use crate::nes::types::*;
|
||||
use image::{ImageBuffer, Rgba};
|
||||
use rand::Rng;
|
||||
use palette::PALETTE;
|
||||
|
||||
pub mod palette;
|
||||
@@ -94,6 +93,7 @@ impl Registers {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct PPU {
|
||||
pub regs: Registers,
|
||||
pub cycle: u16,
|
||||
@@ -105,15 +105,13 @@ pub struct PPU {
|
||||
data_buffer: Byte,
|
||||
}
|
||||
|
||||
// impl PPUMemoryReader for PPU {}
|
||||
|
||||
impl PPU {
|
||||
pub fn new() -> Self {
|
||||
PPU {
|
||||
regs: Registers::new(),
|
||||
cycle: 0,
|
||||
scanline: 0,
|
||||
canvas_main: ImageBuffer::new(256, 240),
|
||||
canvas_main: ImageBuffer::from_pixel(256, 240, PALETTE[&0x00]),
|
||||
pattern_table: [ImageBuffer::new(128, 128), ImageBuffer::new(128, 128)],
|
||||
frame_ready: false,
|
||||
addr_latch_set: false,
|
||||
@@ -162,7 +160,7 @@ impl PPU {
|
||||
// Scanline 240: PPU idle
|
||||
// Scanline 241-260: Vblack. Flag is set during second clock of 241 together
|
||||
// with NMI
|
||||
pub fn clock<T: Memory>(&mut self, _mem: &mut T) {
|
||||
pub fn clock<T: PPUMemory>(&mut self, _mem: &mut T) {
|
||||
if self.cycle == 340 {
|
||||
self.cycle = 0;
|
||||
if self.scanline == 261 {
|
||||
@@ -184,8 +182,8 @@ impl PPU {
|
||||
}
|
||||
}
|
||||
|
||||
// CPU can write certain registers of the PPU through the bus
|
||||
pub fn readb(&mut self, addr: Addr) -> Byte {
|
||||
// read from the main bus
|
||||
pub fn readb<T: PPUMemory>(&mut self, mem: &T, addr: Addr) -> Byte {
|
||||
// Only certain registers of the PPU can actually by read
|
||||
// remaining registers and read attemps will return garbage
|
||||
match addr {
|
||||
@@ -208,7 +206,7 @@ impl PPU {
|
||||
// because the PPU is weird, this does not apply for the
|
||||
// palette memory
|
||||
let data = self.data_buffer;
|
||||
self.data_buffer = self.readb_ppu(addr);
|
||||
self.data_buffer = mem.readb_ppu(addr);
|
||||
|
||||
if addr > 0x3F00 { // everything above 0x3F00 is palette
|
||||
self.data_buffer
|
||||
@@ -220,8 +218,8 @@ impl PPU {
|
||||
}
|
||||
}
|
||||
|
||||
// CPU can write certain registers of the PPU through the bus
|
||||
pub fn writeb(&mut self, addr: Addr, data: Byte) {
|
||||
// // write to the main bus
|
||||
pub fn writeb<T: PPUMemory>(&mut self, mem: &mut T, addr: Addr, data: Byte) {
|
||||
// Only some of the PPU regs can be written to
|
||||
match addr {
|
||||
// Control
|
||||
@@ -253,7 +251,7 @@ impl PPU {
|
||||
}
|
||||
// write data to the ppu addr bus
|
||||
0x2007 => {
|
||||
self.writeb_ppu(self.regs.addr, data);
|
||||
mem.writeb_ppu(self.regs.addr, data);
|
||||
// after write, increment vram addr for next write.
|
||||
// The increment value is determined by the vertical mode
|
||||
// flag of the status reg 0: +1, 1: +32
|
||||
@@ -267,15 +265,6 @@ impl PPU {
|
||||
}
|
||||
}
|
||||
|
||||
// Some read/writes are nt
|
||||
fn writeb_ppu(&mut self, addr: Addr, data: Byte) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
fn readb_ppu(&self, addr: Addr) -> Byte {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// Get the correct color for the pixel from the given palette
|
||||
fn get_color(&self, pixel: Byte, _palette: Byte) -> Pixel {
|
||||
// TODO: not implemented yet. returns some black and white color
|
||||
@@ -327,6 +316,7 @@ impl PPU {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::nes::memory::PPUBus;
|
||||
|
||||
#[test]
|
||||
fn test_set_flags() {
|
||||
@@ -372,14 +362,13 @@ mod tests {
|
||||
#[test]
|
||||
fn test_write_addr() {
|
||||
let mut ppu = PPU::new();
|
||||
// TODO unit test
|
||||
let mut ppu_bus = PPUBus::new();
|
||||
assert_eq!(ppu.regs.addr, 0x0000);
|
||||
|
||||
ppu.writeb(0x2006, 0x12);
|
||||
ppu.writeb(&mut ppu_bus, 0x2006, 0x12);
|
||||
assert_eq!(ppu.regs.addr, 0x1200);
|
||||
ppu.writeb(0x2006, 0x34);
|
||||
ppu.writeb(&mut ppu_bus, 0x2006, 0x34);
|
||||
assert_eq!(ppu.regs.addr, 0x1234);
|
||||
ppu.writeb(0x2006, 0x56);
|
||||
ppu.writeb(&mut ppu_bus, 0x2006, 0x56);
|
||||
assert_eq!(ppu.regs.addr, 0x5634);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user