diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index 639411f..778a35c 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -1,8 +1,14 @@ +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); + /// Return the number of cycles elapsed since the system last reset. fn get_cycle_count(&self) -> u64; diff --git a/src/cpu/mos6502/mod.rs b/src/cpu/mos6502/mod.rs index 347aa38..20b83e6 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::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>, } /// 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,6 +164,10 @@ impl Cpu for Mos6502 { self.cycle_count } + fn attach_trace_handler(&mut self, trace: Box) { + self.trace = Some(trace); + } + /// Execute a single instruction. fn tick(&mut self) -> u8 { let opcode = self.fetch(); 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`. 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..958475e 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) { + self.get_cpu_mut().attach_trace_handler(handler); + } + /// Advance the system by one tick. fn tick(&mut self) -> Duration; diff --git a/src/trace/file.rs b/src/trace/file.rs new file mode 100644 index 0000000..d35a4ad --- /dev/null +++ b/src/trace/file.rs @@ -0,0 +1,23 @@ +use crate::trace::{CpuTrace, TraceHandler}; +use std::{fs::File, io::Write}; + +pub struct FileTraceHandler { + file: File, +} + +impl FileTraceHandler { + pub fn new(filename: String) -> Self { + Self { + file: 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(); + } +} diff --git a/src/trace/mod.rs b/src/trace/mod.rs new file mode 100644 index 0000000..2ace167 --- /dev/null +++ b/src/trace/mod.rs @@ -0,0 +1,14 @@ +#[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); +}