This commit is contained in:
Daniel Bauer
2020-01-18 17:58:15 +01:00
parent 504cc9db9b
commit 1e64f18f22
4 changed files with 358 additions and 70 deletions

View File

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

View File

@@ -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);
}

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}