diff options
| author | Miquel Sabaté Solà <mssola@mssola.com> | 2026-09-08 17:32:08 +0200 |
|---|---|---|
| committer | Miquel Sabaté Solà <mssola@mssola.com> | 2026-09-08 17:32:08 +0200 |
| commit | 1601d0353342230a2a93baa6f008bc16a10137a1 (patch) | |
| tree | 694a754ea4b33a046e639bcd02d35eec1ca86644 | |
| parent | 37c9824cd7674aa9b8b9f094cf3a93e6da2c1f99 (diff) | |
| download | tools.nes-1601d0353342230a2a93baa6f008bc16a10137a1.tar.gz tools.nes-1601d0353342230a2a93baa6f008bc16a10137a1.zip | |
Add an option for setting a cycle limit
This can be quite handy when it comes to checking that a piece of code
doesn't exceed a given amount of cycles, or when you are tuning a block
of code in order to make it go beyond the current amount of cycles.
Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
| -rw-r--r-- | crates/runrom/src/main.rs | 41 | ||||
| -rw-r--r-- | lib/vnf/src/lib.rs | 30 |
2 files changed, 71 insertions, 0 deletions
diff --git a/crates/runrom/src/main.rs b/crates/runrom/src/main.rs index 9040261..f22aa97 100644 --- a/crates/runrom/src/main.rs +++ b/crates/runrom/src/main.rs @@ -17,12 +17,16 @@ struct Args { dump_memory: bool, until_address: u16, halt_on_brk: bool, + cycle_limit: Option<usize>, } fn print_help() { println!("Run an NES/Famicom ROM to test its code under a set of conditions.\n"); println!("usage: runrom [OPTIONS] <FILE>\n"); println!("Options:"); + println!( + " -c, --cycle-limit\t\tSet the limit for how many cycles can be spent. See the allowed values below." + ); println!(" -d, --dump-memory\t\tShow the memory that has changed after a run."); println!(" -f, --function\t\tRun the code by assuming it's a function."); println!(" -h, --help\t\t\tPrint this message and quit."); @@ -31,6 +35,21 @@ fn print_help() { println!(" -s, --start\t\t\tAddress from where to start (default: reset vector)."); println!(" -u, --until-address\t\tRun until the given address is met."); println!(" -v, --version\t\t\tPrint version information."); + + println!("\nAvailable values:"); + println!(" - <integer>\t\t\tA numerical value with the maximum amount of cycles"); + println!( + " - 'nmi-ntsc'\t\t\tNumber of cycles from an NMI to the start of rendering on an NTSC system (round down)." + ); + println!( + " - 'nmi-pal'\t\t\tNumber of cycles from an NMI to the start of rendering on a PAL system (round down)." + ); + println!( + " - 'frame-ntsc'\t\tNumber of cycles to render a full frame on an NTSC system (round down)." + ); + println!( + " - 'frame-pal'\t\t\tNumber of cycles to render a full frame on a PAL system (round down)." + ); std::process::exit(0); } @@ -140,6 +159,25 @@ fn parse_arguments() -> Args { while let Some(arg) = args.next() { match arg.as_str() { "-h" | "--help" => print_help(), + "-c" | "--cycle-limit" => match args.next() { + Some(value) => { + res.cycle_limit = Some(match value.as_str() { + "nmi-ntsc" => vnf::NMI_NTSC_CYCLES, + "nmi-pal" => vnf::NMI_PAL_CYCLES, + "frame-ntsc" => vnf::FRAME_NTSC_CYCLES, + "frame-pal" => vnf::FRAME_NTSC_CYCLES, + _ => match value.parse::<usize>() { + Ok(v) => v, + Err(_) => die(format!( + "invalid integer value '{value}' for the '-c/--cycle-limit' flag" + )), + }, + }); + } + None => { + die("you need to specify a value for the '-c/--cycle-limit' flag".to_string()) + } + }, "-s" | "--start" => { if res.start.is_some() { die("do not specify the '-s/--start' flag twice".to_string()); @@ -268,6 +306,7 @@ fn run( end: u16, assume_function: bool, halt_on_brk: bool, + cycle_limit: Option<usize>, dump_memory: bool, ) -> Result<(), String> { let mut machine = Machine::from(file, start, MemoryPolicy::default())?; @@ -275,6 +314,7 @@ fn run( machine.verbose = true; machine.run_function_mode = assume_function; machine.halt_on_brk = halt_on_brk; + machine.cycle_limit = cycle_limit; machine.until_address(end)?; @@ -313,6 +353,7 @@ fn main() { args.until_address, args.assume_function, args.halt_on_brk, + args.cycle_limit, args.dump_memory, ) { Ok(m) => m, diff --git a/lib/vnf/src/lib.rs b/lib/vnf/src/lib.rs index 0b15d6a..e5e280a 100644 --- a/lib/vnf/src/lib.rs +++ b/lib/vnf/src/lib.rs @@ -31,6 +31,20 @@ use std::path::Path; use xixanta::opcodes::AddressingMode; use xixanta::opcodes::{Instruction, InstructionIdentifier, OPCODES}; +/// Number of cycles from an NMI to the start of rendering on an NTSC system +/// (round down). +pub const NMI_NTSC_CYCLES: usize = 2273; + +/// Number of cycles to render a full frame on an NTSC system (round down). +pub const FRAME_NTSC_CYCLES: usize = 29780; + +/// Number of cycles from an NMI to the start of rendering on a PAL system +/// (round down). +pub const NMI_PAL_CYCLES: usize = 7459; + +/// Number of cycles to render a full frame on a PAL system (round down). +pub const FRAME_PAL_CYCLES: usize = 33247; + /// The 'status' register from the CPU. All flags are set as booleans for easier /// use, and the 'break_mark' byte contains the break mark from the last 'brk' /// instruction. @@ -382,6 +396,10 @@ pub struct Machine { /// Whether execution should be halted _after_ a 'brk' instruction no matter /// what. The break mark will be set accordingly. pub halt_on_brk: bool, + + /// The maximum amount of cycles that the machine should reach before + /// halting. + pub cycle_limit: Option<usize>, } // On success, returns a vector of MemoryCell representing the RAM for a @@ -497,6 +515,7 @@ impl Machine { policy, joypads: [Joypad::default(), Joypad::default()], halt_on_brk: true, + cycle_limit: None, }) } @@ -699,6 +718,17 @@ impl Machine { self.report(); } + // Did we reach the cycle limits? + if let Some(cl) = self.cycle_limit + && cl < self.cycles + { + self.active = false; + if self.verbose { + return Err("reached the limit of cycles available".to_string()); + } + return Ok(()); + } + // After moving the PC, is it out of bounds? if self.pc < 0x8000 { return Err("out of bounds: program counter is pointing below ROM space".to_string()); |
