diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index 616ac7d..c2340c3 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -2,6 +2,7 @@ mod execute; mod fetch; mod registers; use crate::memory::{ActiveInterrupt, Memory, SystemInfo}; +use crate::trace::{CpuTrace, TraceHandler}; use execute::Execute; use fetch::Fetch; use registers::{flags, Registers}; @@ -23,6 +24,7 @@ pub struct Mos6502 { cycle_count: u64, cycles_since_poll: u32, variant: Mos6502Variant, + trace: Option>, } /// Read and write from the system's memory. @@ -142,9 +144,14 @@ impl Mos6502 { cycle_count: 0, cycles_since_poll: 0, variant, + trace: None, } } + pub fn attach_trace_handler(&mut self, trace: Box) { + self.trace = Some(trace); + } + pub fn reset(&mut self) { self.memory.reset(); self.registers.reset(); @@ -162,6 +169,15 @@ impl Mos6502 { /// Execute a single instruction. pub fn tick(&mut self) -> u8 { let opcode = self.fetch(); + + if let Some(handler) = &mut self.trace { + let trace = CpuTrace { + opcode, + address: self.registers.pc.address() - 1, + }; + handler.handle(&trace); + } + match self.execute(opcode) { Ok(cycles) => { self.cycle_count += cycles as u64; 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 77c4e01..978d588 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::Mos6502Variant, systems::klaus::KlausSystemConfig}; + use libnoentiendo::{ + cpu::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 => BasicSystemBuilder::build(romfile.unwrap(), (), platform.provider()), SystemArg::Easy => Easy6502SystemBuilder::build(romfile.unwrap(), (), platform.provider()), SystemArg::Klaus => KlausSystemBuilder::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/basic.rs b/src/systems/basic.rs index f4e90fc..a6c9a81 100644 --- a/src/systems/basic.rs +++ b/src/systems/basic.rs @@ -6,6 +6,7 @@ use crate::memory::{BlockMemory, BranchMemory}; use crate::platform::{PlatformProvider, WindowConfig}; use crate::roms::RomFile; use crate::systems::{System, SystemBuilder}; +use crate::trace::TraceHandler; use std::io::Write; use std::sync::Arc; @@ -91,6 +92,10 @@ pub struct BasicSystem { } impl System for BasicSystem { + fn attach_trace_handler(&mut self, handler: Box) { + self.cpu.attach_trace_handler(handler); + } + fn tick(&mut self) -> Duration { Duration::from_secs_f64(1.0 / 20_000.0) * self.cpu.tick().into() } diff --git a/src/systems/c64/mod.rs b/src/systems/c64/mod.rs index 5670a09..bfddc59 100644 --- a/src/systems/c64/mod.rs +++ b/src/systems/c64/mod.rs @@ -16,6 +16,7 @@ use crate::{ }, platform::{PlatformProvider, WindowConfig}, systems::System, + trace::TraceHandler, }; mod keyboard; @@ -316,6 +317,10 @@ pub struct C64System { } impl System for C64System { + fn attach_trace_handler(&mut self, handler: Box) { + self.cpu.attach_trace_handler(handler); + } + fn tick(&mut self) -> Duration { Duration::from_secs_f64(1.0 / 1_000_000.0) * self.cpu.tick() as u32 } diff --git a/src/systems/easy.rs b/src/systems/easy.rs index a4ca918..67eb5fc 100644 --- a/src/systems/easy.rs +++ b/src/systems/easy.rs @@ -6,6 +6,7 @@ use crate::memory::{ActiveInterrupt, BlockMemory, BranchMemory, Memory, SystemIn use crate::platform::{Color, PlatformProvider, WindowConfig}; use crate::roms::RomFile; use crate::systems::{System, SystemBuilder}; +use crate::trace::TraceHandler; use std::sync::Arc; const WIDTH: u32 = 32; @@ -92,6 +93,10 @@ pub struct Easy6502System { } impl System for Easy6502System { + fn attach_trace_handler(&mut self, handler: Box) { + self.cpu.attach_trace_handler(handler); + } + fn tick(&mut self) -> Duration { Duration::from_secs_f64(1.0 / 20_000.0) * self.cpu.tick().into() } diff --git a/src/systems/klaus.rs b/src/systems/klaus.rs index a43f1d9..91b0206 100644 --- a/src/systems/klaus.rs +++ b/src/systems/klaus.rs @@ -5,6 +5,7 @@ use crate::memory::BlockMemory; use crate::platform::{PlatformProvider, WindowConfig}; use crate::roms::RomFile; use crate::systems::System; +use crate::trace::TraceHandler; use std::cell::Cell; use std::rc::Rc; use std::sync::Arc; @@ -44,6 +45,10 @@ pub struct KlausSystem { } impl System for KlausSystem { + fn attach_trace_handler(&mut self, handler: Box) { + self.cpu.attach_trace_handler(handler); + } + fn tick(&mut self) -> Duration { self.cpu.tick(); if let Some(pc) = &self.pc { diff --git a/src/systems/mod.rs b/src/systems/mod.rs index ad2c153..1bc60b9 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -1,4 +1,7 @@ -use crate::platform::{PlatformProvider, WindowConfig}; +use crate::{ + platform::{PlatformProvider, WindowConfig}, + trace::TraceHandler, +}; use instant::Duration; use std::sync::Arc; @@ -21,6 +24,9 @@ pub trait SystemBuilder { /// A representation of an emulated system. pub trait System { + /// Attach a trace handler to the CPU in this sytem. + fn attach_trace_handler(&mut self, handler: Box); + /// Advance the system by one tick. fn tick(&mut self) -> Duration; diff --git a/src/systems/pet/mod.rs b/src/systems/pet/mod.rs index 222f043..d81af1f 100644 --- a/src/systems/pet/mod.rs +++ b/src/systems/pet/mod.rs @@ -4,6 +4,7 @@ use crate::memory::mos652x::{Pia, Via}; use crate::memory::{BlockMemory, BranchMemory, NullMemory, NullPort, Port, SystemInfo}; use crate::platform::{Color, PlatformProvider, WindowConfig}; use crate::systems::{System, SystemBuilder}; +use crate::trace::TraceHandler; use instant::Instant; use std::cell::Cell; use std::rc::Rc; @@ -210,6 +211,10 @@ pub struct PetSystem { } impl System for PetSystem { + fn attach_trace_handler(&mut self, handler: Box) { + self.cpu.attach_trace_handler(handler); + } + fn tick(&mut self) -> Duration { Duration::from_secs_f64(1.0 / 1_000_000.0) * self.cpu.tick() as u32 } diff --git a/src/systems/vic/mod.rs b/src/systems/vic/mod.rs index 8293a53..8151e0b 100644 --- a/src/systems/vic/mod.rs +++ b/src/systems/vic/mod.rs @@ -9,6 +9,7 @@ use crate::memory::{BlockMemory, BranchMemory, NullMemory, NullPort, Port, Syste use crate::platform::{PlatformProvider, WindowConfig}; use crate::roms::RomFile; use crate::systems::System; +use crate::trace::TraceHandler; use std::cell::{Cell, RefCell}; use std::rc::Rc; use std::sync::Arc; @@ -311,6 +312,10 @@ pub struct Vic20System { } impl System for Vic20System { + fn attach_trace_handler(&mut self, handler: Box) { + self.cpu.attach_trace_handler(handler); + } + fn tick(&mut self) -> instant::Duration { Duration::from_secs_f64(1.0 / 1_000_000.0) * self.cpu.tick() as u32 } diff --git a/src/trace/file.rs b/src/trace/file.rs new file mode 100644 index 0000000..d9ecf38 --- /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(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); +}