diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index 639411f..55e3331 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -1,11 +1,20 @@ +use crate::trace::TraceHandler; + pub mod mos6502; pub trait Cpu { + /// Reset this CPU, clearing internal state. fn reset(&mut self); + /// Attach the given handler to receive trace events from this CPU. + fn attach_trace_handler(&mut self, trace: Box<dyn TraceHandler>); + /// Return the number of cycles elapsed since the system last reset. fn get_cycle_count(&self) -> u64; /// Execute a single instruction. Return the number of cycles elapsed. fn tick(&mut self) -> u8; + + /// Clean up any resources used by this CPU. + fn cleanup(&mut self) -> Result<(), &str>; } diff --git a/src/cpu/mos6502/mod.rs b/src/cpu/mos6502/mod.rs index 347aa38..c1af25a 100644 --- a/src/cpu/mos6502/mod.rs +++ b/src/cpu/mos6502/mod.rs @@ -2,6 +2,7 @@ mod execute; mod fetch; mod registers; use crate::memory::{ActiveInterrupt, Memory}; +use crate::trace::{CpuTrace, TraceHandler}; use execute::Execute; use fetch::Fetch; use registers::{flags, Registers}; @@ -25,6 +26,7 @@ pub struct Mos6502 { cycle_count: u64, cycles_since_poll: u64, variant: Mos6502Variant, + trace: Option<Box<dyn TraceHandler>>, } /// Read and write from the system's memory. @@ -144,6 +146,7 @@ impl Mos6502 { cycle_count: 0, cycles_since_poll: 0, variant, + trace: None, } } } @@ -161,9 +164,21 @@ impl Cpu for Mos6502 { self.cycle_count } + fn attach_trace_handler(&mut self, trace: Box<dyn TraceHandler>) { + self.trace = Some(trace); + } + /// Execute a single instruction. fn tick(&mut self) -> u8 { let opcode = self.fetch(); + + if let Some(tracer) = &mut self.trace { + tracer.handle(&CpuTrace { + address: self.registers.pc.address(), + opcode, + }); + } + match self.execute(opcode) { Ok(cycles) => { self.cycle_count += cycles as u64; @@ -192,4 +207,12 @@ impl Cpu for Mos6502 { } } } + + fn cleanup(&mut self) -> Result<(), &str> { + if let Some(tracer) = &mut self.trace { + tracer.flush() + } else { + Ok(()) + } + } } diff --git a/src/lib.rs b/src/lib.rs index d4fe3ee..401f648 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,9 @@ pub mod roms; /// Systems are created by a [`systems::SystemBuilder`]. A system is created with some roms, configuration, and platform. For instance, the `build` implementation on [`systems::pet::PetSystemBuilder`] takes in [`systems::pet::PetSystemRoms`], [`systems::pet::PetSystemConfig`], and an `Arc<dyn PlatformProvider>`. pub mod systems; +/// Traces log the state of the system as it runs (e.g., to a file). This is useful for debugging. +pub mod trace; + mod time; #[cfg(target_arch = "wasm32")] diff --git a/src/main.rs b/src/main.rs index a3f7814..309fc8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,11 +51,16 @@ struct Args { #[clap(short, long, value_parser, default_value = "symbolic")] key_mapping: KeyMappingArg, + + #[clap(short, long, value_parser, default_value = "false")] + trace: bool, } #[cfg(not(target_arch = "wasm32"))] fn main() { - use libnoentiendo::{cpu::mos6502::Mos6502Variant, systems::klaus::KlausSystemConfig}; + use libnoentiendo::{ + cpu::mos6502::Mos6502Variant, systems::klaus::KlausSystemConfig, trace::file::FileTraceHandler, + }; let args = Args::parse(); @@ -74,7 +79,7 @@ fn main() { KeyMappingArg::Physical => KeyMappingStrategy::Physical, }; - let system = match args.system { + let mut system = match args.system { SystemArg::Basic => BasicSystem::build(romfile.unwrap(), (), platform.provider()), SystemArg::Easy => Easy6502System::build(romfile.unwrap(), (), platform.provider()), SystemArg::Klaus => KlausSystem::build( @@ -105,5 +110,9 @@ fn main() { ), }; + if args.trace { + system.attach_trace_handler(Box::new(FileTraceHandler::new("./cpu.trace".to_owned()))); + } + platform.run(system); } diff --git a/src/systems/mod.rs b/src/systems/mod.rs index 3f7e986..e5097f7 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -1,6 +1,7 @@ use crate::{ cpu::Cpu, platform::{PlatformProvider, WindowConfig}, + trace::TraceHandler, }; use instant::Duration; use std::sync::Arc; @@ -27,6 +28,10 @@ pub trait System { /// Return a mutable reference to the CPU used in this system. fn get_cpu_mut(&mut self) -> Box<&mut dyn Cpu>; + fn attach_trace_handler(&mut self, handler: Box<dyn TraceHandler>) { + self.get_cpu_mut().attach_trace_handler(handler); + } + /// Advance the system by one tick. fn tick(&mut self) -> Duration; @@ -38,6 +43,6 @@ pub trait System { /// Clean up any resources used by this system. fn cleanup(&mut self) -> Result<(), &str> { - Ok(()) + self.get_cpu_mut().cleanup() } } diff --git a/src/trace/file.rs b/src/trace/file.rs new file mode 100644 index 0000000..559602c --- /dev/null +++ b/src/trace/file.rs @@ -0,0 +1,30 @@ +use crate::trace::{CpuTrace, TraceHandler}; +use std::{ + fs::File, + io::{BufWriter, Write}, +}; + +pub struct FileTraceHandler { + file: BufWriter<File>, +} + +impl FileTraceHandler { + pub fn new(filename: String) -> Self { + Self { + file: BufWriter::new(File::create(filename).expect("Invalid filename")), + } + } +} + +impl TraceHandler for FileTraceHandler { + fn handle(&mut self, trace: &CpuTrace) { + self + .file + .write_all(format!("{:04X}: {:02X}\n", trace.address, trace.opcode).as_bytes()) + .unwrap(); + } + + fn flush(&mut self) -> Result<(), &str> { + self.file.flush().map_err(|_| "failed to flush file") + } +} diff --git a/src/trace/mod.rs b/src/trace/mod.rs new file mode 100644 index 0000000..a1b354e --- /dev/null +++ b/src/trace/mod.rs @@ -0,0 +1,19 @@ +#[cfg(not(target_arch = "wasm32"))] +pub mod file; + +/// Trace information provided after each instruction by the CPU. +pub struct CpuTrace { + pub address: u16, + pub opcode: u8, +} + +/// An item which can handle a CPU trace (e.g. logging to a file) +pub trait TraceHandler { + /// Handle a trace event. + fn handle(&mut self, trace: &CpuTrace); + + /// Flush any existing resource buffers. + fn flush(&mut self) -> Result<(), &str> { + Ok(()) + } +}