diff --git a/src/bus.rs b/src/bus.rs index e64a108..6eb2469 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -1,4 +1,4 @@ -use crate::cartridge::Cartridge; +use crate::{cartridge::Cartridge, ppu::Ppu}; pub trait Bus { fn read(&mut self, address: u16) -> u8; @@ -8,32 +8,38 @@ pub trait Bus { pub struct NesBus { ram: [u8; 0x800], // 2KB fixed array, mirrors to fill $0000 to $1FFF cart: Cartridge, + ppu: Ppu, + openbus: u8, // last byte on the CPU data bus } impl NesBus { pub fn new(cart: Cartridge) -> Self { - Self { ram: [0; 0x800], cart } + Self { ram: [0; 0x800], cart, ppu: Ppu::new(), openbus: 0 } } } impl Bus for NesBus { fn read(&mut self, address: u16) -> u8 { - match address { + let result = match address { 0x0000..=0x1FFF => self.ram[(address & 0x07FF) as usize], // Zero-Page my beloved - 0x2000..=0x3FFF => 0, //PPU registers stub, temporal - 0x4000..=0x401F => 0, //APU controller stub, temporal - 0x4020..=0x5FFF => 0, //Expansion ROM stub. temporal + 0x2000..=0x3FFF => self.ppu.read(&mut self.cart, address, self.openbus), + 0x4000..=0x401F => self.openbus, // APU regs: unmapped reads return bus contents + 0x4020..=0x5FFF => self.openbus, // expansion: open bus 0x6000..=0x7FFF => self.cart.read_prg_ram(address), // SRAM reads from the cartridge 0x8000..=0xFFFF => self.cart.read_prg(address), // this is te space PRG Rom gets "loaded" to in real hardware - } + }; + self.openbus = result; + result } fn write(&mut self, address: u16, value: u8) { match address { 0x0000..=0x1FFF => self.ram[(address & 0x07FF) as usize] = value, + 0x2000..=0x3FFF => self.ppu.write(&mut self.cart, address, value), 0x6000..=0x7FFF => self.cart.write_prg_ram(address, value), 0x8000..=0xFFFF => self.cart.write_prg(address, value), _ => {} // Everything else dropped for now } + self.openbus = value; } } \ No newline at end of file diff --git a/src/cartridge.rs b/src/cartridge.rs index f5fea18..496f7ff 100644 --- a/src/cartridge.rs +++ b/src/cartridge.rs @@ -33,10 +33,12 @@ impl Cartridge { let flags7: u8 = data[7]; // Decode bits from the bytes on the ROM - let mirroring: Mirroring = if flags6 & 0b0001 != 0 { Mirroring::Vertical} else { Mirroring::Horizontal }; let battery: bool = flags6 & 0b0010 != 0; let trainer: bool = flags6 & 0b0100 != 0; let four_screen: bool = flags6 & 0b1000 != 0; // this is meant to override mirroring but both will be stored + let mirroring = if four_screen { Mirroring::FourScreen } + else if flags6 & 0b0001 != 0 { Mirroring::Vertical } + else { Mirroring::Horizontal }; let mapper: u8 = ((flags7 & 0xF0) >> 4) << 4 | ((flags6 & 0xF0) >> 4); // Gate for Mapper: NROM (0) and MMC1 (1) supported for now diff --git a/src/main.rs b/src/main.rs index 48282f0..8466f6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,11 +12,29 @@ use crate::cpu::Cpu; fn main() { let args: Vec = std::env::args().collect(); + match args.len() { + 1 => run_nestest(), + 3 if args[1] == "blargg" => run_blargg(&args[2]), + 2 => run_cart(&args[1]), + _ => eprintln!("usage: rnes [ | blargg ]"), + } +} - if args.len() > 1 { - run_blargg(&args[1]); - } else { - run_nestest(); +fn run_cart(path: &str) { + let cart = Cartridge::from_file(path).unwrap(); + let mut bus = NesBus::new(cart); + let mut cpu = Cpu::new(); + cpu.reset(&mut bus); // boot from $FFFC-$FFFD + let mut renderer = PpmRenderer::new(); + + for _ in 0..frames { // a few frames to settle the title screen + while !bus.ppu.frame_done() { + let cycles = cpu.step(&mut bus); + bus.ppu.tick(&mut bus.cart, cycles as u64); // 3 PPU dots per CPU cycle + } + if bus.ppu.take_nmi() { cpu.set_nmi(); } // loop bridges NMI + renderer.present(bus.ppu.framebuffer(), 256, 240); + bus.ppu.begin_frame(); } } diff --git a/src/ppu.rs b/src/ppu.rs index e69de29..31f0e9e 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -0,0 +1,138 @@ +use crate::cartridge::Cartridge; +use crate::mapper::Mirroring; + +pub struct Ppu { + ctrl: u8, // $2000 + mask: u8, // $2001 + status: u8, // $2002 + oamaddr: u8, // $2003 + oam: [u8; 256], // $2004 + scroll_x: u8, // $2005 + scroll_y: u8, + write_latch: bool, // shared W toggle for $2005/$2006, reset by $2002 read + vram_addr: u16, // 14-bit + data_buffer: u8, // $2007 read-buffer + vram: [u8; 0x1000], // 4KB nametables (FourScreen support) + palette: [u8; 32], +} + +impl Ppu { + pub fn new() -> Self { + Self { + ctrl: 0, mask: 0, status: 0, oamaddr: 0, + oam: [0; 256], scroll_x: 0, scroll_y: 0, + write_latch: false, vram_addr: 0, data_buffer: 0, + vram: [0; 0x1000], palette: [0; 32], + } + } + + pub fn read(&mut self, cart: &mut Cartridge, addr: u16, open_bus: u8) -> u8 { + match addr & 0x2007 { + 0x2000 => open_bus, // write-only: returns bus contents + 0x2001 => open_bus, // write-only + 0x2002 => { + let s = (self.status & 0xE0) | (open_bus & 0x1F); + self.status &= !0x80; // reading clears the vblank flag + self.write_latch = false; + s + } + 0x2003 => open_bus, // write-only + 0x2004 => self.oam[self.oamaddr as usize], + 0x2005 => open_bus, // write-only + 0x2006 => open_bus, // write-only + 0x2007 => { + let data = self.vram_read(cart, self.vram_addr); + let result = if self.vram_addr >= 0x3F00 { data } else { self.data_buffer }; + self.data_buffer = data; + self.vram_addr = self.increment_addr(); + result + } + _ => open_bus, + } + } + + pub fn write(&mut self, cart: &mut Cartridge, addr: u16, val: u8) { + match addr & 0x2007 { + 0x2000 => self.ctrl = val, + 0x2001 => self.mask = val, + 0x2002 => {} // write ignored + 0x2003 => self.oamaddr = val, + 0x2004 => { + self.oam[self.oamaddr as usize] = val; + self.oamaddr = self.oamaddr.wrapping_add(1); + } + 0x2005 => { + if !self.write_latch { self.scroll_x = val; } else { self.scroll_y = val; } + self.write_latch = !self.write_latch; + } + 0x2006 => { + if !self.write_latch { + self.vram_addr = (self.vram_addr & 0x00FF) | (((val & 0x3F) as u16) << 8); + } else { + self.vram_addr = (self.vram_addr & 0x7F00) | (val as u16); + } + self.write_latch = !self.write_latch; + } + 0x2007 => { + self.vram_write(cart, self.vram_addr, val); + self.vram_addr = self.increment_addr(); + } + _ => {} + } + } + + fn increment_addr(&self) -> u16 { + let step = if self.ctrl & 0x04 != 0 { 32 } else { 1 }; + self.vram_addr.wrapping_add(step) & 0x3FFF + } + + fn vram_read(&mut self, cart: &mut Cartridge, addr: u16) -> u8 { + match addr { + 0x0000..=0x1FFF => cart.read_chr(addr), // pattern tables -> CHR + 0x2000..=0x3EFF => self.nametable_read(cart, addr), + 0x3F00..=0x3FFF => self.palette_read(addr), + _ => 0, + } + } + + fn vram_write(&mut self, cart: &mut Cartridge, addr: u16, val: u8) { + match addr { + 0x0000..=0x1FFF => cart.write_chr(addr, val), // CHR-RAM writes through + 0x2000..=0x3EFF => { + let off = self.nametable_offset(cart, addr); + self.vram[off] = val; + } + 0x3F00..=0x3FFF => { + let i = palette_index(addr); + self.palette[i] = val; + } + _ => {} + } + } + + fn nametable_offset(&self, cart: &Cartridge, addr: u16) -> usize { + let a = addr & 0x2FFF; // fold the $3000-$3EFF mirror into $2000-$2FFF + let table = match cart.mirroring() { + Mirroring::Horizontal => (a >> 10) & 1, + Mirroring::Vertical => (a >> 11) & 1, + Mirroring::OneScreenLow => 0, + Mirroring::OneScreenHigh => 1, + Mirroring::FourScreen => (a >> 10) & 3, + }; + table as usize * 0x400 + (a & 0x3FF) as usize + } + + fn nametable_read(&self, cart: &Cartridge, addr: u16) -> u8 { + self.vram[self.nametable_offset(cart, addr)] + } + + fn palette_read(&self, addr: u16) -> u8 { + self.palette[palette_index(addr)] + } +} + +fn palette_index(addr: u16) -> usize { + let mut i = (addr & 0x1F) as usize; // $3F20-$3FFF folds into $3F00-$3F1F + if i >= 0x10 && i % 4 == 0 { i -= 0x10; } // 10/14/18/1C mirror to 00/04/08/0C + i +} \ No newline at end of file diff --git a/src/render.rs b/src/render.rs index 468ce33..1f25194 100644 --- a/src/render.rs +++ b/src/render.rs @@ -5,22 +5,21 @@ pub trait Renderer { } pub struct PpmRenderer { - dir: String, frame: u32, } + impl PpmRenderer { - pub fn new(dir: &str) -> Self { - Self { dir: dir.to_string(), frame: 0 } + pub fn new() -> Self { + Self { frame: 0 } } } - impl Renderer for PpmRenderer { fn present(&mut self, framebuffer: &[u8], width: u16, height: u16) { - std::fs::create_dir_all(&self.dir).expect("failed to create render dir"); - let path = format!("{}/frame_{:04}.ppm", self.dir, self.frame); + std::fs::create_dir_all("renders").expect("failed to create renders dir"); + let path = format!("renders/frame_{:04}.ppm", self.frame); std::fs::write(&path, ppm_bytes(framebuffer, width, height)) .expect("failed to write PPM file"); self.frame += 1;