From b80e26f010717c1a837cd1116319971bef26ff22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20HUBERT?= Date: Fri, 5 Apr 2024 09:20:22 +0000 Subject: [PATCH] add tester run command --- .../src/cpu_instruction/microcode/adc.rs | 17 ++-- .../src/cpu_instruction/microcode/and.rs | 5 + .../src/cpu_instruction/microcode/brk.rs | 18 ++-- soft65c02_tester/src/commands.rs | 98 +++++++++++++++++-- soft65c02_tester/src/pest_parser.rs | 3 +- soft65c02_tester/tests/asserter.rs | 2 +- 6 files changed, 116 insertions(+), 27 deletions(-) diff --git a/soft65c02_lib/src/cpu_instruction/microcode/adc.rs b/soft65c02_lib/src/cpu_instruction/microcode/adc.rs index d71c095..e8b22f9 100644 --- a/soft65c02_lib/src/cpu_instruction/microcode/adc.rs +++ b/soft65c02_lib/src/cpu_instruction/microcode/adc.rs @@ -1,14 +1,13 @@ use super::*; -/* - * ADC - Add with carry - * - * The 65C02 has only one instruction for addition, an addition with carry. - * Note: the formula for the oVerflow bit comes from - * http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html - * Method to handle the decimal mode comes from - * http://www.6502.org/tutorials/decimal_mode.html - */ +/// # ADC - Add with carry +/// +/// The 65C02 has only one instruction for addition, an addition with carry. +/// Note: the formula for the oVerflow bit comes from +/// http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html +/// Method to handle the decimal mode comes from +/// http://www.6502.org/tutorials/decimal_mode.html +/// pub fn adc( memory: &mut Memory, registers: &mut Registers, diff --git a/soft65c02_lib/src/cpu_instruction/microcode/and.rs b/soft65c02_lib/src/cpu_instruction/microcode/and.rs index 57ad84e..89dcd60 100644 --- a/soft65c02_lib/src/cpu_instruction/microcode/and.rs +++ b/soft65c02_lib/src/cpu_instruction/microcode/and.rs @@ -1,5 +1,10 @@ use super::*; +/// # AND | Logical AND operation +/// +/// Performs a AND operation between the Accumulator and the specified target. +/// Result is stored in the Accumulator. Affects flags NC. +/// pub fn and( memory: &mut Memory, registers: &mut Registers, diff --git a/soft65c02_lib/src/cpu_instruction/microcode/brk.rs b/soft65c02_lib/src/cpu_instruction/microcode/brk.rs index 0a167df..1d8304a 100644 --- a/soft65c02_lib/src/cpu_instruction/microcode/brk.rs +++ b/soft65c02_lib/src/cpu_instruction/microcode/brk.rs @@ -1,11 +1,17 @@ use super::*; -/* BRK - * Generate a soft interrupt - * - * @see http://6502.org/tutorials/interrupts.html#2.2 - */ - +/// # BRK +/// +/// Generate a [soft interrupt](http://6502.org/tutorials/interrupts.html#2.2). +/// +/// When the processor encounters that instruction, it acts like a hardware +/// interrupt occures (with subtle differences). +/// +/// * Command Pointer register is pushed to the stack pointing 2 bytes after the +/// BRK instruction. +/// * Status register is pushed to the stack with the B flag set . +/// * Status register I flag is set, D flag is cleared. +/// pub fn brk( memory: &mut Memory, registers: &mut Registers, diff --git a/soft65c02_tester/src/commands.rs b/soft65c02_tester/src/commands.rs index 5233156..6b2b786 100644 --- a/soft65c02_tester/src/commands.rs +++ b/soft65c02_tester/src/commands.rs @@ -1,10 +1,10 @@ use anyhow::anyhow; -use soft65c02_lib::{Memory, Registers}; +use soft65c02_lib::{execute_step, Memory, Registers}; use crate::{until_condition::BooleanExpression, AppResult}; pub trait Command { - fn execute(&self, registers: &mut Registers, memory: &mut Memory) -> AppResult; + fn execute(&self, registers: &mut Registers, memory: &mut Memory) -> AppResult>; } #[derive(Debug)] @@ -15,11 +15,11 @@ pub enum CliCommand { } impl Command for CliCommand { - fn execute(&self, registers: &mut Registers, memory: &mut Memory) -> AppResult { + fn execute(&self, registers: &mut Registers, memory: &mut Memory) -> AppResult> { match self { Self::Run(command) => command.execute(registers, memory), Self::Assert(command) => command.execute(registers, memory), - Self::None => Ok(String::new()), + Self::None => Ok(Vec::new()), } } } @@ -31,9 +31,9 @@ pub struct AssertCommand { } impl Command for AssertCommand { - fn execute(&self, registers: &mut Registers, memory: &mut Memory) -> AppResult { + fn execute(&self, registers: &mut Registers, memory: &mut Memory) -> AppResult> { if self.condition.solve(registers, memory) { - Ok(self.comment.clone()) + Ok(vec![self.comment.clone()]) } else { Err(anyhow!(self.comment.clone())) } @@ -47,8 +47,28 @@ pub struct RunCommand { } impl Command for RunCommand { - fn execute(&self, registers: &mut Registers, memory: &mut Memory) -> AppResult { - todo!() + fn execute(&self, registers: &mut Registers, memory: &mut Memory) -> AppResult> { + if let Some(addr) = self.start_address { + registers.command_pointer = addr; + } + + let mut loglines: Vec = Vec::new(); + let mut cp = registers.command_pointer; + + loop { + loglines.push( + execute_step(registers, memory) + .map(|l| l.to_string()) + .map_err(|e| anyhow!(e))?, + ); + + if registers.command_pointer == cp || self.stop_condition.solve(registers, memory) { + break; + } + cp = registers.command_pointer; + } + + Ok(loglines) } } @@ -66,7 +86,7 @@ mod assert_command_tests { let mut memory = Memory::new_with_ram(); match command.execute(&mut registers, &mut memory) { - Ok(s) => assert_eq!("nice comment", s), + Ok(s) => assert_eq!("nice comment", s[0]), Err(_) => panic!("This condition must be valid."), }; } @@ -88,5 +108,65 @@ mod assert_command_tests { #[cfg(test)] mod run_command_tests { + use soft65c02_lib::AddressableIO; + + use crate::until_condition::Source; + use super::*; + + #[test] + fn simple_run() { + let command = RunCommand { + stop_condition: BooleanExpression::Value(true), + start_address: None, + }; + let mut registers = Registers::new_initialized(0x1000); + let mut memory = Memory::new_with_ram(); + memory.write(0x1000, &[0xa9, 0xc0]).unwrap(); // LDA #0xc0 + let loglines = command.execute(&mut registers, &mut memory).unwrap(); + + assert_eq!(1, loglines.len()); + } + + #[test] + fn run_from_addr() { + let command = RunCommand { + stop_condition: BooleanExpression::Value(true), + start_address: Some(0x1234), + }; + let mut registers = Registers::new_initialized(0x0000); + let mut memory = Memory::new_with_ram(); + memory.write(0x1234, &[0xa9, 0xc0]).unwrap(); // LDA #0xc0 + let loglines = command.execute(&mut registers, &mut memory).unwrap(); + + assert_eq!(1, loglines.len()); + } + + #[test] + fn run_with_condition() { + let command = RunCommand { + stop_condition: BooleanExpression::StrictlyGreater(Source::RegisterX, Source::Value(0)), + start_address: Some(0x1234), + }; + let mut registers = Registers::new_initialized(0x1000); + let mut memory = Memory::new_with_ram(); + //memory.write(0x1234, &[0xa9, 0xc0, 0xaa]).unwrap(); // LDA #0xc0; TXA + let loglines = command.execute(&mut registers, &mut memory).unwrap(); + + assert_eq!(2, loglines.len()); + } + + #[test] + fn run_stops_on_loop() { + let command = RunCommand { + stop_condition: BooleanExpression::Value(false), + start_address: None, + }; + let mut registers = Registers::new_initialized(0x1000); + let mut memory = Memory::new_with_ram(); + memory.write(0x1000, &[0xd0, 0b11111110]).unwrap(); // BNE -1 + let loglines = command.execute(&mut registers, &mut memory).unwrap(); + + assert_eq!(1, loglines.len()); + } } diff --git a/soft65c02_tester/src/pest_parser.rs b/soft65c02_tester/src/pest_parser.rs index 34bc971..67604d3 100644 --- a/soft65c02_tester/src/pest_parser.rs +++ b/soft65c02_tester/src/pest_parser.rs @@ -28,8 +28,7 @@ impl RunCommandParser { start_address = Some(parse_memory(&pair.as_str()[3..])?); } Rule::run_until_condition => { - stop_condition = - parse_boolean_condition(pair.into_inner().next().unwrap().into_inner())?; + stop_condition = parse_boolean_condition(pair.into_inner().next().unwrap().into_inner())?; } stmt => panic!("unknown node type {stmt:?}. Is the Pest grammar up to date?"), } diff --git a/soft65c02_tester/tests/asserter.rs b/soft65c02_tester/tests/asserter.rs index fa88be9..a4fc48e 100644 --- a/soft65c02_tester/tests/asserter.rs +++ b/soft65c02_tester/tests/asserter.rs @@ -12,7 +12,7 @@ fn test_assertion() { .execute(&mut registers, &mut memory) .unwrap(); - assert_eq!("The first byte is zero".to_string(), output); + assert_eq!("The first byte is zero".to_string(), output[0]); } #[test]