aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mssola@mssola.com>2026-09-08 17:32:08 +0200
committerMiquel Sabaté Solà <mssola@mssola.com>2026-09-08 17:32:08 +0200
commit1601d0353342230a2a93baa6f008bc16a10137a1 (patch)
tree694a754ea4b33a046e639bcd02d35eec1ca86644
parent37c9824cd7674aa9b8b9f094cf3a93e6da2c1f99 (diff)
downloadtools.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.rs41
-rw-r--r--lib/vnf/src/lib.rs30
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());