diff --git a/Cargo.lock b/Cargo.lock index e1d712c..72f6ced 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,11 +8,13 @@ dependencies = [ "gfx_core 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "gfx_device_gl 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)", "gfx_glyph 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", + "image 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "phf 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "piston2d-opengl_graphics 0.70.0 (registry+https://github.com/rust-lang/crates.io-index)", "piston_window 0.105.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "simple_logger 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index 846b935..5cb9af0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,4 +17,6 @@ piston_window = "0.105.*" piston2d-opengl_graphics = "0.70.*" gfx_core = "0.9.2" gfx_device_gl = "0.16.*" -gfx_glyph = "0.16.*" \ No newline at end of file +gfx_glyph = "0.16.*" +image = "0.22.*" +rand = "0.7.2" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index b62568b..293af36 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,17 @@ #[macro_use] extern crate log; #[macro_use] extern crate failure; #[macro_use] extern crate lazy_static; +extern crate image; extern crate simple_logger; extern crate phf; extern crate piston_window; +extern crate rand; mod nes; use std::path::Path; use piston_window::*; +use nes::NES; use nes::types::*; use nes::cpu::*; use nes::bus::*; @@ -21,7 +24,7 @@ use failure::Error; use gfx_glyph::{Section, GlyphBrushBuilder,GlyphBrush, Scale}; use gfx_device_gl::{Resources,Factory}; -const BG_COLOR: [f32; 4] = [0.0, 0.0, 252.0/256.0, 1.0]; +const BG_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 1.0]; // font options const FT_SIZE_PX: f32 = 11.0; @@ -35,75 +38,72 @@ lazy_static! { fn main() -> Result<(), Error> { simple_logger::init_with_level(Level::Info).unwrap(); - - let mut bus = MemoryBus::new(); + + let mut nes = NES::new(); let cartridge = Path::new("test_roms/nestest.nes"); let cartridge = Cartridge::new(cartridge)?; - bus.insert_cartrige(cartridge); - - // Load little test program into ram - // let test_prog: [Byte; 28] = [0xA2, 0x0A, 0x8E, 0x00, 0x00, 0xA2, 0x03, 0x8E, 0x01, 0x00, 0xAC, 0x00, 0x00, 0xA9, - // 0x00, 0x18, 0x6D, 0x01, 0x00, 0x88, 0xD0, 0xFA, 0x8D, 0x02, 0x00, 0xEA, 0xEA, 0xEA]; - // let offset: Addr = 0x8000; - // // craft test cartrige - // let mut cart = Cartridge::dummy(); - // bus.insert_cartrige(cart); - // for i in 0..test_prog.len() { - // let addr = (i as Addr) + offset; - // bus.writeb(addr, test_prog[i]) - // } - // // hint program location to processor - // bus.writew(0xfffc, offset); + nes.insert_cartrige(cartridge); + nes.cpu.find_pc_addr(&nes.bus); + nes.cpu.regs.pc = 0xC000; // disassemble instructions - let disasm = Disasm::disassemble(&bus, 0xC000, 0xFFFF).unwrap(); - // println!("{:?}", disasm.instructions); - // return; - // let disasm = Disasm::disassemble(&[], 0x000).unwrap(); - - // Create and reset CPU - let mut cpu: CPU = CPU::new(); - cpu.find_pc_addr(&bus); - cpu.regs.pc = 0xC000; - // return Ok(()); - // cpu.reset(&bus); + let disasm = Disasm::disassemble(&nes.bus, 0xC000, 0xFFFF).unwrap(); // Prepare window and drawing resources - let mut window: PistonWindow = WindowSettings::new("NESemu", [256*3, 240*2]) + let mut window: PistonWindow = WindowSettings::new("xXx NESemu xXx", [256*3, 240*2+50]) .exit_on_esc(true).graphics_api(OpenGL::V3_2).build().unwrap(); let mut event_settings = EventSettings::new(); event_settings.max_fps = 60; event_settings.ups = 107400; let mut events = Events::new(event_settings); - // let mut glyphs = get_glyphs(&mut window); + + // font for debug screens let font: &[u8] = include_bytes!("../resources/fonts/PressStart2P.ttf"); let mut glyph_brush: GlyphBrush = GlyphBrushBuilder::using_font_bytes(font) .initial_cache_size((1024, 1024)) .build(window.factory.clone()); - + // main screen texture + let mut texture_ctx = TextureContext { + factory: window.factory.clone(), + encoder: window.factory.create_command_buffer().into(), + }; + let mut texture: G2dTexture = Texture::from_image( + &mut texture_ctx, + &nes.ppu.canvas_main, + &TextureSettings::new() + ).unwrap(); + + // Main loop let mut run = true; while let Some(event) = events.next(&mut window) { if let Some(_) = event.update_args() { // if cpu.regs.pc == 0xC6A2 { run = false } if run { - cpu.clock(&mut bus); + nes.clock(); } } if let Some(_) = event.render_args() { - render(&mut window, &event, &mut glyph_brush, &cpu, &bus, &disasm); + texture.update(&mut texture_ctx, &nes.ppu.canvas_main).unwrap(); + window.draw_2d(&event, |c, g, d| { + clear(BG_COLOR, g); + texture_ctx.encoder.flush(d); + let transform = c.transform.trans(256.0, 50.0).scale(2.0, 2.0); + image(&texture, transform, g); + }); + render_debug(&mut window, &event, &mut glyph_brush, &nes.cpu, &nes.bus, &disasm); } if let Some(Button::Keyboard(key)) = event.press_args() { match key { - Key::C => cpu.clock(&mut bus), // advance one clock + Key::C => nes.clock(), // advance one clock Key::S => { // advance to next instruction - cpu.clock(&mut bus); - while cpu.is_ahead() { - cpu.clock(&mut bus); + nes.clock(); + while nes.cpu.is_ahead() { + nes.clock(); } } - Key::R => cpu.reset(&mut bus), + Key::R => nes.cpu.reset(&mut nes.bus), Key::Space => run = !run, Key::Up => { event_settings.ups = (event_settings.ups as f64 * 1.1) as u64 + 1; @@ -122,24 +122,22 @@ fn main() -> Result<(), Error> { Ok(()) } -fn render(window: &mut PistonWindow, event: &Event, +fn render_debug(window: &mut PistonWindow, event: &Event, glyphs: &mut GlyphBrush, - cpu: &CPU, bus: &MemoryBus, disasm: &Disasm) { - window.draw_2d(event, |c, g, d| { - clear(BG_COLOR, g); - + cpu: &CPU, _bus: &Bus, disasm: &Disasm) { + + window.draw_2d(event, |_c, _g, _d| { let debug_offset = [10.0, 10.0]; render_cpu(glyphs, cpu, debug_offset); render_disasm(glyphs, disasm, cpu.regs.pc, [debug_offset[0], debug_offset[1] + (7.0 * (FT_LINE_DISTANCE+FT_SIZE_PX))]); - render_memory(glyphs, bus, - [debug_offset[0] + 400.0, debug_offset[1]]); - - + // render_memory(glyphs, bus, + // [debug_offset[0] + 400.0, debug_offset[1]]); }); glyphs.use_queue().draw(&mut window.encoder, &window.output_color).unwrap(); window.encoder.flush(&mut window.device); + } fn render_cpu(glyphs: &mut GlyphBrush, cpu: &CPU, offset: [f32; 2]) { @@ -291,7 +289,7 @@ fn render_disasm(glyphs: &mut GlyphBrush, } } -fn render_memory(glyphs: &mut GlyphBrush, bus: &MemoryBus, offset: [f32; 2]) { +fn render_memory(glyphs: &mut GlyphBrush, bus: &Bus, offset: [f32; 2]) { let mut position_y = (offset[0], offset[1]); for page in (0x0000..0x00FF).step_by(16) { position_y.1 += FT_LINE_DISTANCE + FT_SIZE_PX; @@ -325,4 +323,4 @@ fn render_memory(glyphs: &mut GlyphBrush, bus: &MemoryBus, o ..Section::default() }); } -} \ No newline at end of file +} diff --git a/src/nes.rs b/src/nes.rs index e85242e..f3b32d2 100644 --- a/src/nes.rs +++ b/src/nes.rs @@ -1,7 +1,46 @@ +use crate::nes::bus::*; +use crate::nes::cartridge::Cartridge; +use crate::nes::ppu::*; +use crate::nes::cpu::*; + #[allow(non_snake_case)] pub mod cpu; pub mod bus; pub mod types; pub mod disasm; pub mod cartridge; -pub mod mappers; \ No newline at end of file +pub mod mappers; +pub mod ppu; + + +pub struct NES { + pub bus: Bus, + pub cpu: CPU, + pub ppu: PPU, + pub clock_count: u64, +} + +impl NES { + pub fn new() -> Self { + NES { + bus: Bus::new(), + cpu: CPU::new(), + ppu: PPU::new(), + clock_count: 0, + } + } + + pub fn insert_cartrige(&mut self, cartrige: Cartridge) { + self.bus.insert_cartrige(cartrige); + } + + pub fn clock(&mut self) { + self.clock_count += 1; + if self.clock_count % 3 == 0 { + self.cpu.clock(&mut self.bus); + } + self.ppu.clock(&mut self.bus); + + } +} + diff --git a/src/nes/bus.rs b/src/nes/bus.rs index 8ba6a08..23cd539 100644 --- a/src/nes/bus.rs +++ b/src/nes/bus.rs @@ -3,6 +3,8 @@ use crate::nes::cartridge::Cartridge; const RAM_ADDR_RANGE: [Addr; 2] = [0x0000, 0x1fff]; const RAM_PHYS_RANGE: [Addr; 2] = [0x0000, 0x07ff]; +const PPU_ADDR_RANGE: [Addr; 2] = [0x2000, 0x3fff]; +const PPU_PHYS_RANGE: [Addr; 2] = [0x2000, 0x2007]; const CART_ADDR_RANGE: [Addr; 2] = [0x4020, 0xffff]; // Generic interface for a device allowing to read/write memory @@ -20,6 +22,21 @@ pub trait Memory { } } +// 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); + fn readw_ppu(&self, addr: Addr) -> Word { + let lo = self.readb_ppu(addr); + let hi = self.readb_ppu(addr+1); + (hi as Word) << 8 | lo as Word + } + fn writew_ppu(&mut self, addr: Addr, data: Word) { + self.writeb_ppu(addr, data as Byte); + self.writeb_ppu(addr + 1, (data >> 8) as Byte); + } +} + // Impl by devices to access the Bus pub trait BusDevice { fn readb(&self, bus: &T, addr: Addr) -> Byte { @@ -35,6 +52,21 @@ pub trait BusDevice { } } +// Impl by devices to access the Bus +pub trait PPUBusDevice { + fn readb(&self, bus: &T, addr: Addr) -> Byte { + bus.readb_ppu(addr) + } + + fn readw(&self, bus: &T, addr: Addr) -> Word { + bus.readw_ppu(addr) + } + + fn writeb(&mut self, bus: &mut T, addr: Addr, data: Byte) { + bus.writeb_ppu(addr, data) + } +} + // Impl by devices to do stuff on bus clock pub trait Clockable { fn clock(&mut self, bus: &mut T); @@ -43,14 +75,14 @@ pub trait Clockable { // A simple bus giving access to a chunk of memory // and the cartrige -pub struct MemoryBus { +pub struct Bus { ram: [Byte; 0x0800], // 2kb cartrige: Option } -impl MemoryBus { - pub fn new() -> MemoryBus { - MemoryBus { +impl Bus { + pub fn new() -> Bus { + Bus { ram: [0; 0x0800], cartrige: None, } @@ -61,7 +93,7 @@ impl MemoryBus { } } -impl Memory for MemoryBus { +impl Memory for Bus { fn readb(&self, addr: Addr) -> Byte { if let Some(cartrige) = &self.cartrige { @@ -70,6 +102,42 @@ impl Memory for MemoryBus { } } + if RAM_ADDR_RANGE[0] <= addr && addr <= RAM_ADDR_RANGE[1] { + // Ram is 3x mirrored after 0x07ff + return self.ram[(addr & RAM_PHYS_RANGE[1]) as usize] + } + if PPU_ADDR_RANGE[0] <= addr && addr <= PPU_ADDR_RANGE[1] { + // PPU memory is mirrored after 0x2007 to 0x3fff + return self.ram[(addr & PPU_PHYS_RANGE[1]) as usize] + } + 0x0000 // generic response + } + + fn writeb(&mut self, addr: Addr, data: Byte) { + if let Some(cartrige) = &mut self.cartrige { + if CART_ADDR_RANGE[0] <= addr && addr <= CART_ADDR_RANGE[1] { + cartrige.writeb(addr, data) + } + } + if RAM_ADDR_RANGE[0] <= addr && addr <= RAM_ADDR_RANGE[1] { + // Ram is 3x mirrored after 07ff + self.ram[(addr & RAM_PHYS_RANGE[1]) as usize] = data + } + if PPU_ADDR_RANGE[0] <= addr && addr <= PPU_ADDR_RANGE[1] { + // PPU memory is mirrored after 0x2007 to 0x3fff + self.ram[(addr & PPU_PHYS_RANGE[1]) as usize] = data + } + } +} + +impl PPUMemory for Bus { + fn readb_ppu(&self, addr: Addr) -> Byte { + if let Some(cartrige) = &self.cartrige { + if CART_ADDR_RANGE[0] <= addr && addr <= CART_ADDR_RANGE[1] { + return cartrige.readb(addr) + } + } + if RAM_ADDR_RANGE[0] <= addr && addr <= RAM_ADDR_RANGE[1] { // Ram is 3x mirrored after 07ff return self.ram[(addr & 0x07ff) as usize] @@ -77,7 +145,7 @@ impl Memory for MemoryBus { 0x0000 // generic response } - fn writeb(&mut self, addr: Addr, data: Byte) { + fn writeb_ppu(&mut self, addr: Addr, data: Byte) { if let Some(cartrige) = &mut self.cartrige { if CART_ADDR_RANGE[0] <= addr && addr <= CART_ADDR_RANGE[1] { cartrige.writeb(addr, data) @@ -105,7 +173,7 @@ mod tests { #[test] fn test_read_write_ram() { - let mut bus = MemoryBus::new(); + let mut bus = Bus::new(); // read/write to ram addr bus.writeb(0x0000, 1); @@ -120,7 +188,7 @@ mod tests { #[test] fn test_read_write_cartrige() { - let mut bus = MemoryBus::new(); + let mut bus = Bus::new(); // read/write non-existant to cartrige bus.writeb(0x4030, 3); diff --git a/src/nes/disasm.rs b/src/nes/disasm.rs index 01a830a..9223359 100644 --- a/src/nes/disasm.rs +++ b/src/nes/disasm.rs @@ -17,7 +17,7 @@ impl BusDevice for Disasm {} impl Disasm { // Disassemble given code region - pub fn disassemble(mem: &MemoryBus, start: Addr, stop: Addr) -> Result { + pub fn disassemble(mem: &Bus, start: Addr, stop: Addr) -> Result { let mut instructions = Vec::new(); let mut addresses = Vec::new(); let mut mem_iter = ((start as usize) .. (stop as usize)+1).map({|a| a as Addr}); diff --git a/src/nes/ppu.rs b/src/nes/ppu.rs new file mode 100644 index 0000000..cd52dc2 --- /dev/null +++ b/src/nes/ppu.rs @@ -0,0 +1,30 @@ +use crate::nes::bus::Memory; +use crate::nes::bus::Clockable; +use image::{ImageBuffer, Rgba}; +use rand::Rng; + +pub struct PPU { + pub canvas_main: ImageBuffer, Vec> +} + +impl PPU { + pub fn new() -> Self { + PPU { + canvas_main: ImageBuffer::new(256, 240), + } + } +} + +impl Clockable for PPU { + fn clock(&mut self, bus: &mut T) { + // update random pixel + let mut rng = rand::thread_rng(); + let x = rng.gen_range(0, self.canvas_main.width()); + let y = rng.gen_range(0, self.canvas_main.height()); + let rgba = Rgba([rng.gen_range(0,255), rng.gen_range(0,255), rng.gen_range(0,255), 255]); + self.canvas_main.put_pixel(x, y, rgba); + } +} + + +