diff --git a/Cargo.lock b/Cargo.lock index 685b9d4..0ec2636 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,12 +50,24 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "4.5.1" @@ -173,6 +185,7 @@ dependencies = [ "byteorder", "clap", "log", + "sdl2", "simple_logger", ] @@ -200,6 +213,29 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "sdl2" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8356b2697d1ead5a34f40bcc3c5d3620205fe0c7be0a14656223bfeec0258891" +dependencies = [ + "bitflags", + "lazy_static", + "libc", + "sdl2-sys", +] + +[[package]] +name = "sdl2-sys" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26bcacfdd45d539fb5785049feb0038a63931aa896c7763a2a12e125ec58bd29" +dependencies = [ + "cfg-if", + "libc", + "version-compare", +] + [[package]] name = "serde" version = "1.0.197" @@ -294,6 +330,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index 0a05fc5..12d85d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ clap = { version = "4.5", features = ["derive"] } log = "0.4.20" simple_logger = "4.3" byteorder = "1.5" -#sdl2 = "0.36.0" +sdl2 = "0.36.0" [profile.release] #strip = true diff --git a/src/device/cpu.rs b/src/device/cpu.rs deleted file mode 100644 index ff058ab..0000000 --- a/src/device/cpu.rs +++ /dev/null @@ -1,65 +0,0 @@ -use byteorder::{BigEndian, ByteOrder}; -use crate::device::cpu::Instruction::{ClearScreen, JumpTo, SetRegister}; - -#[derive(Eq, PartialEq, Debug)] -enum Instruction { - ClearScreen, - /// Jump to location - JumpTo(u16), - SetRegister(usize, u8), - AddValueToRegister(usize, u8), - SetIndex(u16), - /// - DRAW(usize, usize, u8), -} - -impl Instruction { - pub fn parse_fetched_instruction(location: &[u8]) -> Instruction { - assert_eq!(location.len(), 2); - let instruction = BigEndian::read_u16(location); - - if instruction == 0xe0 { - return ClearScreen; - } else if (instruction & 0x1000) == 0x1000 { - return JumpTo(instruction & 0xfff); - } else if (instruction & 0x6000) == 0x6000 { - return SetRegister(((instruction & 0x0f00)>>8) as usize, (instruction & 0xff) as u8); - } else { - todo!(); - } - } -} - -#[cfg(test)] -mod tests { - use crate::device::cpu::Instruction; - use crate::device::cpu::Instruction::{ClearScreen, JumpTo, SetRegister}; - - #[test] - fn test_clear_screen() { - let instruction_bytes = 0x00e0_u16.to_be_bytes(); - let ins = Instruction::parse_fetched_instruction(&instruction_bytes); - assert_eq!(ins, ClearScreen); - } - - #[test] - fn test_jump_to_instruction_1() { - let instruction_bytes = 0x1123_u16.to_be_bytes(); - let ins = Instruction::parse_fetched_instruction(&instruction_bytes); - assert_eq!(ins, JumpTo(0x123)); - } - - #[test] - fn test_jump_to_instruction_2() { - let instruction_bytes = 0x1faf_u16.to_be_bytes(); - let ins = Instruction::parse_fetched_instruction(&instruction_bytes); - assert_eq!(ins, JumpTo(0xfaf)); - } - - #[test] - fn test_set_instruction(){ - let instruction_bytes = 0x6a00_u16.to_be_bytes(); - let ins = Instruction::parse_fetched_instruction(&instruction_bytes); - assert_eq!(ins, SetRegister(10,0)); - } -} \ No newline at end of file diff --git a/src/device/device.rs b/src/device/device.rs index ef5ca57..66b5b2c 100644 --- a/src/device/device.rs +++ b/src/device/device.rs @@ -1,8 +1,10 @@ use std::ops::SubAssign; use std::sync::atomic::{AtomicU16, Ordering}; use std::sync::{Arc, Mutex, RwLock}; +use std::thread; use std::thread::{JoinHandle, sleep}; use std::time::Duration; +use crate::device::instruction::Instruction; use crate::device::timer::Timer; @@ -10,24 +12,30 @@ pub struct Device { pub registers: RegisterFile, pub memory: Box<[u8; Self::DEVICE_MEMORY_SIZE]>, pub timer: Timer, - pub stack: Vec + pub stack: Vec, + pub frame_buffer: Arc>> } impl Device { - pub const DEVICE_MEMORY_SIZE: usize = 2 << 12; - pub fn new(timer: Timer) -> Device { + pub const DEVICE_MEMORY_SIZE: usize = 1 << 12; + pub const FRAME_BUFFER_WIDTH: usize = 64; + pub const FRAME_BUFFER_HEIGHT: usize = 32; + pub const FRAME_BUFFER_SIZE: usize = Self::FRAME_BUFFER_WIDTH*Self::FRAME_BUFFER_HEIGHT; + pub fn new(timer: Timer, fb: Arc>>) -> Device { let memory = vec![0u8; Self::DEVICE_MEMORY_SIZE].into_boxed_slice().try_into().unwrap(); log::trace!("Successfully initiated device memory"); Device { registers: RegisterFile::new(), memory, - stack:Vec::with_capacity(16), + frame_buffer: fb, + stack: Vec::with_capacity(16), timer, } } } -impl Device{ - const DEFAULT_FONT:[u8;5*16] = [ + +impl Device { + const DEFAULT_FONT: [u8; 5 * 16] = [ 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 0x20, 0x60, 0x20, 0x20, 0x70, // 1 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 @@ -45,30 +53,63 @@ impl Device{ 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E 0xF0, 0x80, 0xF0, 0x80, 0x80 // F ]; - const FONT_DEFAULT_MEM_LOCATION_START:usize = 0x50; - const FONT_DEFAULT_MEM_LOCATION_END:usize = 0x9F; - const ROM_START:usize = 0x200; - pub fn cycle(&mut self){ - + const FONT_DEFAULT_MEM_LOCATION_START: usize = 0x50; + const FONT_DEFAULT_MEM_LOCATION_END: usize = 0x9F; + const ROM_START: usize = 0x200; + pub fn cycle(&mut self) { + let pc = self.registers.pc as usize; + let instr_slice = self.memory.get(pc..pc + 2).expect("Failed to get memory"); + self.registers.pc += 2; + let instruction = Instruction::decode_instruction(instr_slice); + self.execute_instruction(instruction); + } + pub fn execute_instruction(&mut self, instruction: Instruction) { + // thread::sleep(Duration::from_millis(250)); + log::trace!("Executing {:?}, {:?}",&instruction,&self.registers); + match instruction{ + Instruction::PassThrough => { + log::info!("Executing passthrough"); + } + Instruction::ClearScreen => { + log::info!("ClearScreen") + } + Instruction::JumpTo(new_pc) => { + self.registers.pc = new_pc; + } + Instruction::SetRegister(reg_location, value) => { + self.registers.v[reg_location] = value; + } + Instruction::AddValueToRegister(reg_location, value) => { + self.registers.v[reg_location] += value; + } + Instruction::SetIndex(value) => { + self.registers.i = value; + } + Instruction::Draw(x, y, n) => { + let frame_buffer = self.frame_buffer.lock(); + log::warn!("Draw call unimplemented"); + } + }; } - pub fn set_default_font(&mut self){ + pub fn set_default_font(&mut self) { log::info!("Loaded default font from memory"); self.memory[Self::FONT_DEFAULT_MEM_LOCATION_START..=Self::FONT_DEFAULT_MEM_LOCATION_END].copy_from_slice(&Self::DEFAULT_FONT); } /// load a rom from bytes - pub fn load_rom(&mut self,rom: &[u8]){ + pub fn load_rom(&mut self, rom: &[u8]) { log::info!("Loaded ROM from memory"); self.memory[Self::ROM_START..].copy_from_slice(rom); } - } -impl Drop for Device{ + +impl Drop for Device { fn drop(&mut self) { self.timer.send_stop_signal() } } +#[derive(Debug)] pub struct RegisterFile { pub v: [u8; 0x10], /// program counter - only u12 technically. @@ -78,7 +119,7 @@ pub struct RegisterFile { } impl RegisterFile { - pub const DEFAULT_PC_VALUE:u16 = Device::ROM_START as u16; + pub const DEFAULT_PC_VALUE: u16 = Device::ROM_START as u16; pub fn new() -> RegisterFile { RegisterFile { v: [0; 0x10], diff --git a/src/device/instruction.rs b/src/device/instruction.rs new file mode 100644 index 0000000..31412d4 --- /dev/null +++ b/src/device/instruction.rs @@ -0,0 +1,131 @@ +use byteorder::{BigEndian, ByteOrder}; +use crate::device::instruction::Instruction::{AddValueToRegister, ClearScreen, Draw, JumpTo, PassThrough, SetIndex, SetRegister}; + +#[derive(Eq, PartialEq, Debug)] +pub enum Instruction { + /// Blanket instruction that does nothing + PassThrough, + /// 00E0 - Clear the screen + ClearScreen, + /// 1NNN - Jump to location + JumpTo(u16), + /// 6XNN - Set register to value + SetRegister(usize, u8), + /// 7XNN - Add value to register + AddValueToRegister(usize, u8), + /// ANNN - Set index value + SetIndex(u16), + /// + Draw(usize, usize, u8), +} + +impl Instruction { + pub fn decode_instruction(location: &[u8]) -> Instruction { + assert_eq!(location.len(), 2); + let instruction = BigEndian::read_u16(location); + let outer_instruction_nibble = (instruction & 0xF000) >> 12; + match outer_instruction_nibble { + 0x0 if instruction == 0xe0 => { + ClearScreen + } + 0x0 => { + log::warn!("Ignoring unsupported instruction {}",instruction); + PassThrough + } + 0x1 => { + JumpTo(instruction & 0xfff) + } + 0x6 => { + SetRegister(((instruction & 0x0f00) >> 8) as usize, (instruction & 0xff) as u8) + } + 0x7 => { + AddValueToRegister(((instruction & 0x0f00) >> 8) as usize, (instruction & 0xff) as u8) + } + 0xA => { + SetIndex(instruction & 0xfff) + } + 0xD => { + let x = (instruction & 0xf00) >> 8; + let y = (instruction & 0xf0) >> 4; + let n = instruction & 0xf; + Draw(x as usize, y as usize, n as u8) + } + 0x8 => { + todo!("Arithmetic instructions pending") + } + _ => { + todo!("Unimplemented instruction") + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::device::instruction::Instruction; + use crate::device::instruction::Instruction::{AddValueToRegister, ClearScreen, Draw, JumpTo, SetIndex, SetRegister}; + + #[test] + fn test_clear_screen() { + let instruction_bytes = 0x00e0_u16.to_be_bytes(); + let ins = Instruction::decode_instruction(&instruction_bytes); + assert_eq!(ins, ClearScreen); + } + + #[test] + #[should_panic] + fn test_other_0x0nnn_instructions_panic() { + let instruction_bytes = 0x00f0_u16.to_be_bytes(); + Instruction::decode_instruction(&instruction_bytes); + } + + + #[test] + fn test_jump_to_instruction_1() { + let instruction_bytes = 0x1123_u16.to_be_bytes(); + let ins = Instruction::decode_instruction(&instruction_bytes); + assert_eq!(ins, JumpTo(0x123)); + } + + #[test] + fn test_jump_to_instruction_2() { + let instruction_bytes = 0x1faf_u16.to_be_bytes(); + let ins = Instruction::decode_instruction(&instruction_bytes); + assert_eq!(ins, JumpTo(0xfaf)); + } + + #[test] + fn test_set_register_instruction() { + let instruction_bytes = 0x6a00_u16.to_be_bytes(); + let ins = Instruction::decode_instruction(&instruction_bytes); + assert_eq!(ins, SetRegister(10, 0)); + } + + #[test] + fn test_set_register_instruction_2() { + let instruction_bytes = 0x6f23_u16.to_be_bytes(); + let ins = Instruction::decode_instruction(&instruction_bytes); + assert_eq!(ins, SetRegister(15, 0x23)); + } + + #[test] + fn test_add_register_instruction_2() { + let instruction_bytes = 0x7f23_u16.to_be_bytes(); + let ins = Instruction::decode_instruction(&instruction_bytes); + assert_eq!(ins, AddValueToRegister(15, 0x23)); + } + + #[test] + fn test_set_index() { + let instruction_bytes = 0xafaf_u16.to_be_bytes(); + let ins = Instruction::decode_instruction(&instruction_bytes); + assert_eq!(ins, SetIndex(0xfaf)); + } + + #[test] + fn test_draw() { + let instruction_bytes = 0xdfab_u16.to_be_bytes(); + let ins = Instruction::decode_instruction(&instruction_bytes); + assert_eq!(ins, Draw(0xf, 0xa, 0xb)) + } +} \ No newline at end of file diff --git a/src/device/mod.rs b/src/device/mod.rs index a603634..7d0ff85 100644 --- a/src/device/mod.rs +++ b/src/device/mod.rs @@ -1,6 +1,6 @@ pub mod timer; mod device; mod sound; -mod cpu; +pub mod instruction; pub use device::*; diff --git a/src/kb_map.rs b/src/kb_map.rs new file mode 100644 index 0000000..bddcd0c --- /dev/null +++ b/src/kb_map.rs @@ -0,0 +1,24 @@ +use sdl2::keyboard::Keycode; + +/// get index of key pressed. 0..9+A..F provides a u8 +pub fn get_key_index(p0: Keycode) -> Option { + match p0 { + Keycode::Kp0 => Some(0x0), + Keycode::Kp1 => Some(0x1), + Keycode::Kp2 => Some(0x2), + Keycode::Kp3 => Some(0x3), + Keycode::Kp4 => Some(0x4), + Keycode::Kp5 => Some(0x5), + Keycode::Kp6 => Some(0x6), + Keycode::Kp7 => Some(0x7), + Keycode::Kp8 => Some(0x8), + Keycode::Kp9 => Some(0x9), + Keycode::A => Some(0xA), + Keycode::B => Some(0xB), + Keycode::C => Some(0xC), + Keycode::D => Some(0xD), + Keycode::E => Some(0xE), + Keycode::F => Some(0xF), + _ => None + } +} diff --git a/src/main.rs b/src/main.rs index a3e15ba..cc640ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,23 @@ +use std::fs::File; +use std::io::Read; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; use log::LevelFilter; +use sdl2::audio::{AudioQueue, AudioSpecDesired}; +use sdl2::event::Event; +use sdl2::EventPump; +use sdl2::keyboard::Keycode; +use sdl2::pixels::Color; +use sdl2::render::{BlendMode, WindowCanvas}; use simple_logger::SimpleLogger; use device::timer::Timer; use crate::device::Device; +use crate::kb_map::get_key_index; mod args; mod device; +mod kb_map; fn main() { SimpleLogger::new().with_level(LevelFilter::Info).env().init().unwrap(); @@ -13,10 +26,117 @@ fn main() { let mut timer = Timer::new(); timer.start(); - let mut device = Device::new(timer); - device.set_default_font(); - let mut i = 0; + let frame_buffer = get_frame_buffer(); + let frame_buffer_for_display = Arc::clone(&frame_buffer); + let (sender,receiver) = std::sync::mpsc::channel(); + + let compute_handle = thread::spawn(move ||{ + + let mut device = Device::new(timer, frame_buffer); + device.set_default_font(); + { + let rom = load_rom(); + device.load_rom(&rom); + } + + loop { + let val = receiver.try_recv(); + if let Ok(()) = val{ + break; + }else if let Err(std::sync::mpsc::TryRecvError::Disconnected) = val{ + panic!("Disconnected"); + } + device.cycle(); + } + }); + + let (mut canvas, mut event_pump) = initiate_sdl(2f32); + + canvas.set_draw_color(Color::BLACK); + canvas.clear(); + 'running: loop { + for event in event_pump.poll_iter() { + match event { + Event::Quit { .. } | + Event::KeyDown { keycode: Some(Keycode::Escape), .. } => { + sender.send(()).expect("Could not send"); + break 'running; + } + Event::KeyDown { keycode: Some(x), repeat: false, .. } => { + if let Some(key_val) = get_key_index(x) { + log::info!("Key+ {}",key_val) + } + } + Event::KeyUp { keycode: Some(x), repeat: false, .. } => { + if let Some(key_val) = get_key_index(x) { + log::info!("Key- {}",key_val) + } + } + _ => {} + } + } + + + // The rest of the game loop goes here... + { + let lock = frame_buffer_for_display.lock().expect("Failed to get Display"); + log::info!("Framebuffer status: {:?}",lock); + } + canvas.present(); + + + // 60fps - small offset to consider for cpu cycle time + std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60 - 2000_000)); + } + + + compute_handle.join().unwrap(); + +} + +fn get_frame_buffer() -> Arc>> { + Arc::new(Mutex::new(vec![0u8; Device::FRAME_BUFFER_SIZE].into_boxed_slice().try_into().unwrap())) +} + +const ROM_SIZE:usize = 4096 - 0x200; +fn load_rom()->[u8;ROM_SIZE]{ + let mut rom_slice = [0u8;ROM_SIZE]; + let mut file = File::open("roms/ibm_logo.ch8").expect("could not open"); + file.read(&mut rom_slice).expect("Unwrap"); + rom_slice + +} + +fn initiate_sdl(draw_scale:f32) -> (WindowCanvas, EventPump) { + let sdl_context = sdl2::init().unwrap(); + let video_subsystem = sdl_context.video().unwrap(); + // let audio_subsystem = sdl_context.audio().unwrap(); + // let wanted_spec = AudioSpecDesired { + // channels: Some(1), + // samples: Some(256), + // freq: Some(15360), + // }; + // let audio_queue = audio_subsystem.open_queue::(None, &wanted_spec).unwrap(); + // audio_queue.resume(); + let window_width = (Device::FRAME_BUFFER_WIDTH as f32 * draw_scale) as u32; + let window_height = (Device::FRAME_BUFFER_HEIGHT as f32 * draw_scale) as u32; + + let window = video_subsystem.window("byte-pusher-emu", window_width,window_height) + .position_centered() + .build() + .unwrap(); + let mut canvas = window.into_canvas().build().unwrap(); + + canvas.set_scale(draw_scale, draw_scale).expect("Setting scale"); + + canvas.set_blend_mode(BlendMode::None); + canvas.clear(); + canvas.present(); + let event_pump = sdl_context.event_pump().unwrap(); + (canvas, event_pump + // , audio_queue + ) }