diff --git a/src/lib.rs b/src/lib.rs index 5077800..1804803 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,12 +12,19 @@ pub mod cpu; pub mod error; pub mod io; +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Chip8 { + pub cpu: cpu::CPU, + pub bus: bus::Bus, +} /// Common imports for chumpulator pub mod prelude { use super::*; + pub use super::Chip8; pub use crate::bus; - pub use cpu::{disassemble::Disassemble, CPU}; - pub use io::{*, WindowBuilder}; pub use bus::{Bus, Read, Region::*, Write}; + pub use cpu::{disassemble::Disassemble, ControlFlags, CPU}; + pub use error::Result; + pub use io::{WindowBuilder, *}; } diff --git a/src/main.rs b/src/main.rs index f40d0e1..f7276ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,112 +36,164 @@ struct Arguments { pub step: Option, } +#[derive(Debug)] +struct State { + pub speed: usize, + pub step: Option, + pub rate: u64, + pub ch8: Chip8, + pub win: Window, + pub fb: FrameBuffer, + pub ft: Instant, +} + +impl State { + fn new(options: Arguments) -> Result { + let mut state = State { + speed: options.speed, + step: options.step, + rate: options.frame_rate, + ch8: Chip8 { bus: bus! { + // Load the charset into ROM + Charset [0x0050..0x00A0] = include_bytes!("mem/charset.bin"), + // Load the ROM file into RAM + Program [0x0200..0x1000] = &read(options.file)?, + // Create a screen + Screen [0x1000..0x1100], + // Create a stack + Stack [0x0EA0..0x0F00], + }, + cpu: CPU::new( + 0x1000, + 0x50, + 0x200, + 0xefe, + Disassemble::default(), + options.breakpoints, + ControlFlags { + authentic: options.authentic, + debug: options.debug, + pause: options.pause, + ..Default::default() + }, + )}, + win: WindowBuilder::default().build()?, + fb: FrameBuffer::new(64, 32), + ft: Instant::now(), + }; + state.fb.render(&mut state.win, &state.ch8.bus); + Ok(state) + } + fn tick_cpu(&mut self) { + if !self.ch8.cpu.flags.pause { + let rate = self.speed; + match self.step { + Some(ticks) => { + self.ch8.cpu.multistep(&mut self.ch8.bus, ticks, rate); + // Pause the CPU and clear step + self.ch8.cpu.flags.pause = true; + self.step = None; + }, + None => { + self.ch8.cpu.multistep(&mut self.ch8.bus, rate, rate); + }, + } + } + } + fn frame(&mut self) -> Option<()> { + { + if self.ch8.cpu.flags.pause { + self.win.set_title("Chirp ⏸") + } else { + self.win.set_title("Chirp ▶"); + } + // update framebuffer + self.fb.render(&mut self.win, &mut self.ch8.bus); + // get key input (has to happen after render) + chirp::io::get_keys(&mut self.win, &mut self.ch8.cpu); + } + Some(()) + } + fn keys(&mut self) -> Option<()> { + // handle keybinds for the UI + for key in self.win.get_keys_pressed(KeyRepeat::No) { + use Key::*; + match key { + F1 | Comma => self.ch8.cpu.dump(), + F2 | Period => self + .ch8.bus + .print_screen() + .expect("The 'screen' memory region exists"), + F3 => {chirp::io::debug_dump_screen(&self.ch8.bus).expect("Unable to write debug screen dump"); eprintln!("Screen dumped to file.")}, + F4 | Slash => { + eprintln!( + "{}", + endis("Debug", { + self.ch8.cpu.flags.debug(); + self.ch8.cpu.flags.debug + }) + ) + } + F5 | Backslash => eprintln!( + "{}", + endis("Pause", { + self.ch8.cpu.flags.pause(); + self.ch8.cpu.flags.pause + }) + ), + F6 | Enter => { + eprintln!("Step"); + self.ch8.cpu.singlestep(&mut self.ch8.bus); + } + F7 => { + eprintln!("Set breakpoint {:x}", self.ch8.cpu.pc()); + self.ch8.cpu.set_break(self.ch8.cpu.pc()); + } + F8 => { + eprintln!("Unset breakpoint {:x}", self.ch8.cpu.pc()); + self.ch8.cpu.unset_break(self.ch8.cpu.pc()); + } + F9 | Delete => { + eprintln!("Soft reset state.cpu {:x}", self.ch8.cpu.pc()); + self.ch8.cpu.soft_reset(); + self.ch8.bus.clear_region(Screen); + } + F10 | Backspace => { + eprintln!("Hard reset state.cpu"); + self.ch8.cpu = CPU::default(); + self.ch8.bus.clear_region(Screen); + } + Escape => return None, + _ => (), + } + } + Some(()) + } + fn wait_for_next_frame(&mut self) { + let rate = 1_000_000_000 / self.rate + 1; + std::thread::sleep(Duration::from_nanos(rate).saturating_sub(self.ft.elapsed())); + self.ft = Instant::now(); + } +} + +impl Iterator for State { + type Item = (); + + fn next(&mut self) -> Option { + self.wait_for_next_frame(); + self.keys()?; + self.tick_cpu(); + self.frame(); + Some(()) + } } fn main() -> Result<()> { let options = Arguments::parse_args_default_or_exit(); + let state = State::new(options)?; + Ok(for _ in state {}) +} - // Create the data bus - let mut bus = bus! { - // Load the charset into ROM - "charset" [0x0050..0x00A0] = include_bytes!("mem/charset.bin"), - // Load the ROM file into RAM - "userram" [0x0200..0x0F00] = &read(options.file)?, - // Create a screen - "screen" [0x0F00..0x1000], - // Create some stack memory - //"stack" [0x2000..0x2100], - }; - - let mut cpu = CPU::new(0xf00, 0x50, 0x200, 0x20fe, Disassemble::default()); - for point in options.breakpoints { - cpu.set_break(point); - } - cpu.flags.authentic = options.authentic; - cpu.flags.debug = options.debug; - cpu.flags.pause = options.pause; - let mut framebuffer = FrameBuffer::new(64, 32); - let mut window = WindowBuilder::default().build()?; - let mut frame_time = Instant::now(); - let mut step_time = Instant::now(); - - framebuffer.render(&mut window, &mut bus); - - cpu.flags.pause = false; - cpu.flags.debug = true; - - loop { - if !cpu.flags.pause { - cpu.tick(&mut bus); - } - while frame_time.elapsed() > Duration::from_micros(16000) { - if cpu.flags.pause { - window.set_title("Chirp ⏸") - } else { - window.set_title("Chirp ▶") - } - frame_time += Duration::from_micros(16000); - // tick sound and delay timers - cpu.tick_timer(); - // update framebuffer - framebuffer.render(&mut window, &mut bus); - // get key input (has to happen after framebuffer) - get_keys(&mut window, &mut cpu); - // handle keys at the - for key in window.get_keys_pressed(KeyRepeat::No) { - use Key::*; - match key { - F1 => cpu.dump(), - F2 => bus - .print_screen() - .expect("The 'screen' memory region exists"), - F3 => { - println!( - "{}", - endis("Debug", { - cpu.flags.debug(); - cpu.flags.debug - }) - ) - } - F4 => println!( - "{}", - endis("Pause", { - cpu.flags.pause(); - cpu.flags.pause - }) - ), - F5 => { - println!("Step"); - cpu.singlestep(&mut bus) - } - F6 => { - println!("Set breakpoint {:x}", cpu.pc()); - cpu.set_break(cpu.pc()) - } - F7 => { - println!("Unset breakpoint {:x}", cpu.pc()); - cpu.unset_break(cpu.pc()) - } - F8 => { - println!("Soft reset CPU {:x}", cpu.pc()); - cpu.soft_reset(); - bus.clear_region("screen"); - } - F9 => { - println!("Hard reset CPU"); - cpu = CPU::default(); - bus.clear_region("screen"); - } - Escape => return Ok(()), - _ => (), - } - } - } - std::thread::sleep(Duration::from_micros(1666).saturating_sub(step_time.elapsed())); - step_time = Instant::now(); - } - //Ok(()) /// Parses a hexadecimal string into a u16 fn parse_hex(value: &str) -> std::result::Result { u16::from_str_radix(value, 16)