From 8d506773bfa87c5412f4e08c37356e2062e7e570 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 19 Jun 2023 12:27:35 +0200 Subject: [PATCH 01/13] Add assembly example code This should make it easier for beginners to understand how to test this emulator. --- README.md | 51 ++++++++++++++++++ examples/asm/euclid/euclid.a65 | 33 ++++++++++++ examples/asm/euclid/euclid.bin | Bin 0 -> 34 bytes examples/asm/linker.cfg | 11 ++++ examples/euclid.rs | 36 +++++++++++++ .../{euclidean_algo.rs => euclid_bytes.rs} | 2 +- 6 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 examples/asm/euclid/euclid.a65 create mode 100644 examples/asm/euclid/euclid.bin create mode 100644 examples/asm/linker.cfg create mode 100644 examples/euclid.rs rename examples/{euclidean_algo.rs => euclid_bytes.rs} (93%) diff --git a/README.md b/README.md index 0f7f207..e86e3b8 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,57 @@ fn main() { } ``` +The same can be achieved, by compiling the euclid example yourself. + +First install a 6502 assembler and linker, e.g. [cc65](https://cc65.github.io/cc65/). + +```sh +brew install cc65 +``` + +Then compile and link the assembly file: + +```sh +cd examples/asm/euclid +ca65 euclid.asm +ld65 -C ../linker.cfg -o euclid.bin euclid.o +``` + +This will create a binary file `euclid.bin` that you can load into the emulator: + +```rust +use mos6502::memory::Bus; +use mos6502::memory::Memory; +use mos6502::cpu; +use std::fs::read; + +fn main() { + // Calculate the greatest common divisor of 56 and 49 + // using Euclid's algorithm. + let zero_page_data = [56, 49]; + + // Load the binary file from disk + let program = match read("examples/asm/euclid/euclid.bin") { + Ok(data) => data, + Err(err) => { + println!("Error reading euclid.bin: {}", err); + return; + } + }; + + let mut cpu = cpu::CPU::new(Memory::new()); + + cpu.memory.set_bytes(0x00, &zero_page_data); + cpu.memory.set_bytes(0x10, &program); + cpu.registers.program_counter = 0x10; + + cpu.run(); + + // The expected GCD is 7 + assert_eq!(7, cpu.registers.accumulator); +} +``` + ## Credits This started off as a fork of [amw-zero/6502-rs](https://github.com/amw-zero/6502-rs), diff --git a/examples/asm/euclid/euclid.a65 b/examples/asm/euclid/euclid.a65 new file mode 100644 index 0000000..150847c --- /dev/null +++ b/examples/asm/euclid/euclid.a65 @@ -0,0 +1,33 @@ +; euclid.a65 +; A program to find the greatest common divisor of two numbers + +.ORG $1000 + +; .algo +LDA $00 ; Load from F to A +; .algo_ +sec ; Set carry flag +SBC $01 ; Subtract S from the number in A (from F) +BEQ end ; Jump to .end if the difference is zero +BMI swap ; Jump to .swap if the difference is negative +STA $00 ; Load A to F +JMP algo_ ; Jump to .algo_ + +; .end +end: +LDA $00 ; Load from F to A +BRK ; Break (end program) + +; .swap +swap: +LDX $00 ; Load F to X +LDY $01 ; Load S to Y +STX $01 ; Store X to S +STY $00 ; Store Y to F +JMP algo ; Jump to .algo + +algo: +JMP algo ; Infinite loop to prevent program from ending + +algo_: +JMP algo_ ; Infinite loop to prevent program from ending diff --git a/examples/asm/euclid/euclid.bin b/examples/asm/euclid/euclid.bin new file mode 100644 index 0000000000000000000000000000000000000000..0c957020e5fe177cf5965ac0de3daf7535b8709d GIT binary patch literal 34 ncmZ3=VDXgk1G@o7D}#@`z)}W=WeiIg+ZbCId}IVbNL~N{s9*?L literal 0 HcmV?d00001 diff --git a/examples/asm/linker.cfg b/examples/asm/linker.cfg new file mode 100644 index 0000000..0a82281 --- /dev/null +++ b/examples/asm/linker.cfg @@ -0,0 +1,11 @@ +MEMORY { + ZP: start = $0000, size = $0100, type = rw, define = yes; + RAM: start = $0100, size = $0200, type = rw, define = yes; + ROM: start = $8000, size = $8000, type = ro; +} + +SEGMENTS { + ZEROPAGE: load = ZP, type = zp; + DATA: load = RAM, type = rw; + CODE: load = ROM, type = ro; +} diff --git a/examples/euclid.rs b/examples/euclid.rs new file mode 100644 index 0000000..97a301e --- /dev/null +++ b/examples/euclid.rs @@ -0,0 +1,36 @@ +use mos6502::cpu; +use mos6502::memory::Bus; +use mos6502::memory::Memory; +use std::fs::read; + +fn main() { + println!("Enter two numbers (< 128) separated by a space to know their GCD."); + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + + let zero_page_data = input + .split_whitespace() + .map(|s| s.parse::().unwrap()) + .collect::>(); + + // Load the binary file from disk + let program = match read("examples/asm/euclid/euclid.bin") { + Ok(data) => data, + Err(err) => { + println!("Error reading euclid.bin: {}", err); + // print all files in the current directory + println!("Files in current directory:"); + return; + } + }; + + let mut cpu = cpu::CPU::new(Memory::new()); + + cpu.memory.set_bytes(0x00, &zero_page_data); + cpu.memory.set_bytes(0x10, &program); + cpu.registers.program_counter = 0x10; + + cpu.run(); + + println!("GCD is {}", cpu.registers.accumulator); +} diff --git a/examples/euclidean_algo.rs b/examples/euclid_bytes.rs similarity index 93% rename from examples/euclidean_algo.rs rename to examples/euclid_bytes.rs index 04fc087..d261fa0 100644 --- a/examples/euclidean_algo.rs +++ b/examples/euclid_bytes.rs @@ -5,7 +5,7 @@ use mos6502::memory::Bus; use mos6502::memory::Memory; fn main() { - println!("Enter two numbers (< 128) to know their GCD:"); + println!("Enter two numbers (< 128) separated by a space to know their GCD."); let mut input = String::new(); std::io::stdin().read_line(&mut input).unwrap(); From fa0b343c69a08c24f625c9a679a46f873ed8756b Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 19 Jun 2023 13:08:07 +0200 Subject: [PATCH 02/13] cleanup --- examples/euclid.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/euclid.rs b/examples/euclid.rs index 97a301e..5636deb 100644 --- a/examples/euclid.rs +++ b/examples/euclid.rs @@ -18,8 +18,6 @@ fn main() { Ok(data) => data, Err(err) => { println!("Error reading euclid.bin: {}", err); - // print all files in the current directory - println!("Files in current directory:"); return; } }; From dbfa32b5c2dd7c41991aaf597a0e49cc6838f6cc Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 19 Jun 2023 13:32:35 +0200 Subject: [PATCH 03/13] fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e86e3b8..fc3570b 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Then compile and link the assembly file: ```sh cd examples/asm/euclid -ca65 euclid.asm +ca65 euclid.a65 ld65 -C ../linker.cfg -o euclid.bin euclid.o ``` From 23ddc109eea3032dd663a521973e3bcbd86115cf Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 19 Jun 2023 13:34:35 +0200 Subject: [PATCH 04/13] Add functional test and update CPU debug output The functional test is based on https://github.com/Klaus2m5/6502_65C02_functional_tests It does NOT pass yet. :( However, I think it's a good idea to have it in the repo nonetheless, to make some incremental improvements. This PR is based on the `asm` branch, which should be merged first. --- .../functional_test/6502_functional_test.bin | Bin 0 -> 65536 bytes examples/asm/functional_test/README.md | 1 + examples/functional.rs | 34 ++++++++++++++++++ src/cpu.rs | 6 +--- 4 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 examples/asm/functional_test/6502_functional_test.bin create mode 100644 examples/asm/functional_test/README.md create mode 100644 examples/functional.rs diff --git a/examples/asm/functional_test/6502_functional_test.bin b/examples/asm/functional_test/6502_functional_test.bin new file mode 100644 index 0000000000000000000000000000000000000000..8a20227a6decf69bcdf87155cace7278caa2996a GIT binary patch literal 65536 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 Hf9U>yF)>V1 literal 0 HcmV?d00001 diff --git a/examples/asm/functional_test/README.md b/examples/asm/functional_test/README.md new file mode 100644 index 0000000..9c7dafc --- /dev/null +++ b/examples/asm/functional_test/README.md @@ -0,0 +1 @@ +From https://github.com/Klaus2m5/6502_65C02_functional_tests \ No newline at end of file diff --git a/examples/functional.rs b/examples/functional.rs new file mode 100644 index 0000000..83b2d70 --- /dev/null +++ b/examples/functional.rs @@ -0,0 +1,34 @@ +use mos6502::cpu; +use mos6502::memory::Bus; +use mos6502::memory::Memory; +use std::fs::read; + +fn main() { + // Load the binary file from disk + let program = match read("examples/asm/functional_test/6502_functional_test.bin") { + Ok(data) => data, + Err(err) => { + println!("Error reading functional test: {}", err); + return; + } + }; + + let mut cpu = cpu::CPU::new(Memory::new()); + + cpu.memory.set_bytes(0x00, &program); + cpu.registers.program_counter = 0x400; + + // run step-by-step + let mut old_pc = cpu.registers.program_counter; + while cpu.registers.program_counter != 0x3468 { + cpu.single_step(); + println!("{cpu:?}"); + + if cpu.registers.program_counter == old_pc { + println!("Infinite loop detected!"); + break; + } + + old_pc = cpu.registers.program_counter; + } +} diff --git a/src/cpu.rs b/src/cpu.rs index b3fc2af..4ba4f90 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -988,11 +988,7 @@ impl CPU { impl core::fmt::Debug for CPU { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!( - f, - "CPU Dump:\n\nAccumulator: {}", - self.registers.accumulator - ) + write!(f, "CPU {{ registers: {:?}", self.registers) } } From cb49e5e5a6fcbaca5959d429904a732a4a8b40c7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 19 Jun 2023 13:41:13 +0200 Subject: [PATCH 05/13] Print decoded instruction --- examples/functional.rs | 6 ++++++ src/instruction.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/functional.rs b/examples/functional.rs index 83b2d70..87ab0f3 100644 --- a/examples/functional.rs +++ b/examples/functional.rs @@ -21,6 +21,12 @@ fn main() { // run step-by-step let mut old_pc = cpu.registers.program_counter; while cpu.registers.program_counter != 0x3468 { + // Use `fetch_next_and_decode` instead of + // `single_step` to see the decoded instruction + if let Some(decoded_instr) = cpu.fetch_next_and_decode() { + println!("{decoded_instr:?}"); + cpu.execute_instruction(decoded_instr); + } cpu.single_step(); println!("{cpu:?}"); diff --git a/src/instruction.rs b/src/instruction.rs index 885452e..c80396e 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -105,7 +105,7 @@ pub enum Instruction { TYA, // Transfer Y to Accumulator..... | N. ...Z. A = Y } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub enum OpInput { UseImplied, UseImmediate(u8), From f61a36f798c0baf7638289db5bd93f08a7b284c5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 19 Jun 2023 19:10:46 +0200 Subject: [PATCH 06/13] Extend documentation --- examples/asm/functional_test/README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/examples/asm/functional_test/README.md b/examples/asm/functional_test/README.md index 9c7dafc..ad31b85 100644 --- a/examples/asm/functional_test/README.md +++ b/examples/asm/functional_test/README.md @@ -1 +1,14 @@ -From https://github.com/Klaus2m5/6502_65C02_functional_tests \ No newline at end of file +# 6502 Functional Test + +This binary was taken from +https://github.com/Klaus2m5/6502_65C02_functional_tests +Copyright (C) 2013 Klaus Dormann + +This is a test suite for 6502/65C02/65C816 processors. It contains a plethora of +tests, which covers all documented opcodes. + +Note that the binary was not built from the source code in the repository, but +pre-built binaries were used instead. That is because the source code is not +compatible with the assembler used by the [cc65](https://cc65.github.io/cc65/) +toolchain. + From a7765431cd23f05041644631010c7e417cb59503 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 28 Jun 2023 10:05:11 +0200 Subject: [PATCH 07/13] extend docs --- examples/asm/functional_test/README.md | 23 +++++++++++++++++++---- examples/functional.rs | 2 +- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/examples/asm/functional_test/README.md b/examples/asm/functional_test/README.md index ad31b85..8b457be 100644 --- a/examples/asm/functional_test/README.md +++ b/examples/asm/functional_test/README.md @@ -1,9 +1,5 @@ # 6502 Functional Test -This binary was taken from -https://github.com/Klaus2m5/6502_65C02_functional_tests -Copyright (C) 2013 Klaus Dormann - This is a test suite for 6502/65C02/65C816 processors. It contains a plethora of tests, which covers all documented opcodes. @@ -12,3 +8,22 @@ pre-built binaries were used instead. That is because the source code is not compatible with the assembler used by the [cc65](https://cc65.github.io/cc65/) toolchain. +## Building + +```bash +make build +``` + +This will create a `6502_functional_test.bin` file in the `build` directory, +which the emulator will load. + +## Credits + +Taken from +https://github.com/amb5l/6502_65C02_functional_tests +which is a CA65-compatible port of +https://github.com/Klaus2m5/6502_65C02_functional_tests + +The original source code was written by Klaus Dormann, and is licensed under +the GPL-3.0 license. +``` diff --git a/examples/functional.rs b/examples/functional.rs index 87ab0f3..acea8ce 100644 --- a/examples/functional.rs +++ b/examples/functional.rs @@ -24,7 +24,7 @@ fn main() { // Use `fetch_next_and_decode` instead of // `single_step` to see the decoded instruction if let Some(decoded_instr) = cpu.fetch_next_and_decode() { - println!("{decoded_instr:?}"); + println!("{decoded_instr}"); cpu.execute_instruction(decoded_instr); } cpu.single_step(); From fa6e4f49f176dfb2d49d5f00c101dc33e571b8a2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 28 Jun 2023 10:12:09 +0200 Subject: [PATCH 08/13] Compile functional test from sources --- .../functional_test/6502_functional_test.bin | Bin 65536 -> 13154 bytes .../functional_test/6502_functional_test.ca65 | 6125 +++++++++++++++++ examples/asm/functional_test/Makefile | 3 + examples/asm/functional_test/README.md | 8 + 4 files changed, 6136 insertions(+) create mode 100644 examples/asm/functional_test/6502_functional_test.ca65 create mode 100644 examples/asm/functional_test/Makefile diff --git a/examples/asm/functional_test/6502_functional_test.bin b/examples/asm/functional_test/6502_functional_test.bin index 8a20227a6decf69bcdf87155cace7278caa2996a..24ce1ce199eac3e55e943569f1d254cc83846adb 100644 GIT binary patch literal 13154 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 Hf9U>yF)>V1 diff --git a/examples/asm/functional_test/6502_functional_test.ca65 b/examples/asm/functional_test/6502_functional_test.ca65 new file mode 100644 index 0000000..255f765 --- /dev/null +++ b/examples/asm/functional_test/6502_functional_test.ca65 @@ -0,0 +1,6125 @@ +; +; 6 5 0 2 F U N C T I O N A L T E S T +; +; Copyright (C) 2012-2020 Klaus Dormann +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . + + +; This program is designed to test all opcodes of a 6502 emulator using all +; addressing modes with focus on propper setting of the processor status +; register bits. +; +; version 05-jan-2020 +; contact info at http://2m5.de or email K@2m5.de +; +; assembled with CA65, linked with LD65 (cc65.github.io): +; ca65 -l 6502_functional_test.lst 6502_functional_test.ca65 +; ld65 6502_functional_test.o -o 6502_functional_test.bin \ +; -m 6502_functional_test.map -C example.cfg +; example linker config (example.cfg): +; MEMORY { +; RAM: start = $0000, size=$8000, type = rw, fill = yes, \ +; fillval = $FF, file = %O; +; ROM: start = $8000, size=$7FFA, type = ro, fill = yes, \ +; fillval = $FF, file = %O; +; ROM_VECTORS: start = $FFFA, size=6, type = ro, fill = yes, \ +; fillval = $FF, file = %O; +; } +; SEGMENTS { +; ZEROPAGE: load=RAM, type=rw; +; DATA: load=RAM, type=rw, offset=$0200; +; CODE: load=RAM, type=rw, offset=$0400; +; VECTORS: load=ROM_VECTORS, type=ro; +; } +; +; No IO - should be run from a monitor with access to registers. +; To run load intel hex image with a load command, than alter PC to 400 hex +; (code_segment) and enter a go command. +; Loop on program counter determines error or successful completion of test. +; Check listing for relevant traps (jump/branch *). +; Please note that in early tests some instructions will have to be used before +; they are actually tested! +; +; RESET, NMI or IRQ should not occur and will be trapped if vectors are enabled. +; Tests documented behavior of the original NMOS 6502 only! No unofficial +; opcodes. Additional opcodes of newer versions of the CPU (65C02, 65816) will +; not be tested. Decimal ops will only be tested with valid BCD operands and +; N V Z flags will be ignored. +; +; Debugging hints: +; Most of the code is written sequentially. if you hit a trap, check the +; immediately preceeding code for the instruction to be tested. Results are +; tested first, flags are checked second by pushing them onto the stack and +; pulling them to the accumulator after the result was checked. The "real" +; flags are no longer valid for the tested instruction at this time! +; If the tested instruction was indexed, the relevant index (X or Y) must +; also be checked. Opposed to the flags, X and Y registers are still valid. +; +; versions: +; 28-jul-2012 1st version distributed for testing +; 29-jul-2012 fixed references to location 0, now #0 +; added license - GPLv3 +; 30-jul-2012 added configuration options +; 01-aug-2012 added trap macro to allow user to change error handling +; 01-dec-2012 fixed trap in branch field must be a branch +; 02-mar-2013 fixed PLA flags not tested +; 19-jul-2013 allowed ROM vectors to be loaded when load_data_direct = 0 +; added test sequence check to detect if tests jump their fence +; 23-jul-2013 added RAM integrity check option +; 16-aug-2013 added error report to standard output option +; 13-dec-2014 added binary/decimal opcode table switch test +; 14-dec-2014 improved relative address test +; 23-aug-2015 added option to disable self modifying tests +; 24-aug-2015 all self modifying immediate opcodes now execute in data RAM +; added small branch offset pretest +; 21-oct-2015 added option to disable decimal mode ADC & SBC tests +; 04-dec-2017 fixed BRK only tested with interrupts enabled +; added option to skip the remainder of a failing test +; in report.i65 +; 05-jan-2020 fixed shifts not testing zero result and flag when last 1-bit +; is shifted out + +; C O N F I G U R A T I O N + +;ROM_vectors writable (0=no, 1=yes) +;if ROM vectors can not be used interrupts will not be trapped +;as a consequence BRK can not be tested but will be emulated to test RTI +ROM_vectors = 0 + +;load_data_direct (0=move from code segment, 1=load directly) +;loading directly is preferred but may not be supported by your platform +;0 produces only consecutive object code, 1 is not suitable for a binary image +load_data_direct = 1 + +;I_flag behavior (0=force enabled, 1=force disabled, 2=prohibit change, 3=allow +;change) 2 requires extra code and is not recommended. SEI & CLI can only be +;tested if you allow changing the interrupt status (I_flag = 3) +I_flag = 1 + +;configure memory - try to stay away from memory used by the system +;zero_page memory start address, $52 (82) consecutive Bytes required +; add 2 if I_flag = 2 +zero_page = $a + +;data_segment memory start address, $7B (123) consecutive Bytes required +; check that this matches the linker configuration file +data_segment = $200 + .if (data_segment & $ff) <> 0 + .error "low byte of data_segment MUST be $00 !!" + .endif + +;code_segment memory start address, 13.1kB of consecutive space required +; add 2.5 kB if I_flag = 2 +; check that this matches the linker configuration file +code_segment = $400 + +;self modifying code may be disabled to allow running in ROM +;0=part of the code is self modifying and must reside in RAM +;1=tests disabled: branch range +disable_selfmod = 1 + +;report errors through I/O channel (0=use standard self trap loops, 1=include +;report.i65 as I/O channel, add 3.5 kB) +report = 0 + +;RAM integrity test option. Checks for undesired RAM writes. +;set lowest non RAM or RAM mirror address page (-1=disable, 0=64k, $40=16k) +;leave disabled if a monitor, OS or background interrupt is allowed to alter RAM +ram_top = -1 + +;disable test decimal mode ADC & SBC, 0=enable, 1=disable, +;2=disable including decimal flag in processor status +disable_decimal = 0 + +;macros for error & success traps to allow user modification +;example: +; .macro trap +; jsr my_error_handler +; .endmacro +; .macro trap_eq +; bne :+ +; trap ;failed equal (zero) +;: +; .endmacro +; +; my_error_handler should pop the calling address from the stack and report it. +; putting larger portions of code (more than 3 bytes) inside the trap macro +; may lead to branch range problems for some tests. + .if report = 0 + .macro trap + jmp * ;failed anyway + .endmacro + .macro trap_eq + beq * ;failed equal (zero) + .endmacro + .macro trap_ne + bne * ;failed not equal (non zero) + .endmacro + .macro trap_cs + bcs * ;failed carry set + .endmacro + .macro trap_cc + bcc * ;failed carry clear + .endmacro + .macro trap_mi + bmi * ;failed minus (bit 7 set) + .endmacro + .macro trap_pl + bpl * ;failed plus (bit 7 clear) + .endmacro + .macro trap_vs + bvs * ;failed overflow set + .endmacro + .macro trap_vc + bvc * ;failed overflow clear + .endmacro +; please observe that during the test the stack gets invalidated +; therefore a RTS inside the success macro is not possible + .macro success + jmp * ;test passed, no errors + .endmacro + .endif + .if report = 1 + .macro trap + jsr report_error + .endmacro + .macro trap_eq + bne :+ + trap ;failed equal (zero) +: + .endmacro + .macro trap_ne + beq :+ + trap ;failed not equal (non zero) +: + .endmacro + .macro trap_cs + bcc :+ + trap ;failed carry set +: + .endmacro + .macro trap_cc + bcs :+ + trap ;failed carry clear +: + .endmacro + .macro trap_mi + bpl :+ + trap ;failed minus (bit 7 set) +: + .endmacro + .macro trap_pl + bmi :+ + trap ;failed plus (bit 7 clear) +: + .endmacro + .macro trap_vs + bvc :+ + trap ;failed overflow set +: + .endmacro + .macro trap_vc + bvs :+ + trap ;failed overflow clear +: + .endmacro +; please observe that during the test the stack gets invalidated +; therefore a RTS inside the success macro is not possible + .macro success + jsr report_success + .endmacro + .endif + + .define equ = + +carry equ %00000001 ;flag bits in status +zero equ %00000010 +intdis equ %00000100 +decmode equ %00001000 +break equ %00010000 +reserv equ %00100000 +overfl equ %01000000 +minus equ %10000000 + +fc equ carry +fz equ zero +fzc equ carry+zero +fv equ overfl +fvz equ overfl+zero +fn equ minus +fnc equ minus+carry +fnz equ minus+zero +fnzc equ minus+zero+carry +fnv equ minus+overfl + +fao equ break+reserv ;bits always on after PHP, BRK +fai equ fao+intdis ;+ forced interrupt disable +faod equ fao+decmode ;+ ignore decimal +faid equ fai+decmode ;+ ignore decimal +m8 equ $ff ;8 bit mask +m8i equ $ff&~intdis ;8 bit mask - interrupt disable + +;macros to allow masking of status bits. +;masking test of decimal bit +;masking of interrupt enable/disable on load and compare +;masking of always on bits after PHP or BRK (unused & break) on compare + .if disable_decimal < 2 + .if I_flag = 0 + .macro load_flag p1 + lda #p1&m8i ;force enable interrupts (mask I) + .endmacro + .macro cmp_flag p1 + cmp #(p1|fao)&m8i ;I_flag is always enabled + always on bits + .endmacro + .macro eor_flag p1 + eor #(p1&m8i|fao) ;mask I, invert expected flags + always on bits + .endmacro + .endif + .if I_flag = 1 + .macro load_flag p1 + lda #p1|intdis ;force disable interrupts + .endmacro + .macro cmp_flag p1 + cmp #(p1|fai)&m8 ;I_flag is always disabled + always on bits + .endmacro + .macro eor_flag p1 + eor #(p1|fai) ;invert expected flags + always on bits + I + .endmacro + .endif + .if I_flag = 2 + .macro load_flag p1 + lda #p1 + ora flag_I_on ;restore I-flag + and flag_I_off + .endmacro + .macro cmp_flag p1 + eor flag_I_on ;I_flag is never changed + cmp #(p1|fao)&m8i ;expected flags + always on bits, mask I + .endmacro + .macro eor_flag p1 + eor flag_I_on ;I_flag is never changed + eor #(p1&m8i|fao) ;mask I, invert expected flags + always on bits + .endmacro + .endif + .if I_flag = 3 + .macro load_flag p1 + lda #p1 ;allow test to change I-flag (no mask) + .endmacro + .macro cmp_flag p1 + cmp #(p1|fao)&m8 ;expected flags + always on bits + .endmacro + .macro eor_flag p1 + eor #p1|fao ;invert expected flags + always on bits + .endmacro + .endif + .else + .if I_flag = 0 + .macro load_flag p1 + lda #p1&m8i ;force enable interrupts (mask I) + .endmacro + .macro cmp_flag p1 + ora #decmode ;ignore decimal mode bit + cmp #(p1|faod)&m8i ;I_flag is always enabled + always on bits + .endmacro + .macro eor_flag p1 + ora #decmode ;ignore decimal mode bit + eor #(p1&m8i|faod) ;mask I, invert expected flags + always on bits + .endmacro + .endif + .if I_flag = 1 + .macro load_flag p1 + lda #p1|intdis ;force disable interrupts + .endmacro + .macro cmp_flag p1 + ora #decmode ;ignore decimal mode bit + cmp #(p1|faid)&m8 ;I_flag is always disabled + always on bits + .endmacro + .macro eor_flag p1 + ora #decmode ;ignore decimal mode bit + eor #(p1|faid) ;invert expected flags + always on bits + I + .endmacro + .endif + .if I_flag = 2 + .macro load_flag p1 + lda #p1 + ora flag_I_on ;restore I-flag + and flag_I_off + .endmacro + .macro cmp_flag p1 + eor flag_I_on ;I_flag is never changed + ora #decmode ;ignore decimal mode bit + cmp #(p1|faod)&m8i ;expected flags + always on bits, mask I + .endmacro + .macro eor_flag p1 + eor flag_I_on ;I_flag is never changed + ora #decmode ;ignore decimal mode bit + eor #(p1&m8i|faod) ;mask I, invert expected flags + always on bits + .endmacro + .endif + .if I_flag = 3 + .macro load_flag p1 + lda #p1 ;allow test to change I-flag (no mask) + .endmacro + .macro cmp_flag p1 + ora #decmode ;ignore decimal mode bit + cmp #(p1|faod)&m8 ;expected flags + always on bits + .endmacro + .macro eor_flag p1 + ora #decmode ;ignore decimal mode bit + eor #p1|faod ;invert expected flags + always on bits + .endmacro + .endif + .endif + +;macros to set (register|memory|zeropage) & status + .macro set_stat p1 ;setting flags in the processor status register + load_flag p1 + pha ;use stack to load status + plp + .endmacro + + .macro set_a p1,p2 ;precharging accu & status + load_flag p2 + pha ;use stack to load status + lda #p1 ;precharge accu + plp + .endmacro + + .macro set_x p1,p2 ;precharging index & status + load_flag p2 + pha ;use stack to load status + ldx #p1 ;precharge index x + plp + .endmacro + + .macro set_y p1,p2 ;precharging index & status + load_flag p2 + pha ;use stack to load status + ldy #p1 ;precharge index y + plp + .endmacro + + .macro set_ax p1,p2 ;precharging indexed accu & immediate status + load_flag p2 + pha ;use stack to load status + lda p1,x ;precharge accu + plp + .endmacro + + .macro set_ay p1,p2 ;precharging indexed accu & immediate status + load_flag p2 + pha ;use stack to load status + lda p1,y ;precharge accu + plp + .endmacro + + .macro set_z p1,p2 ;precharging indexed zp & immediate status + load_flag p2 + pha ;use stack to load status + lda p1,x ;load to zeropage + sta zpt + plp + .endmacro + + .macro set_zx p1,p2 ;precharging zp,x & immediate status + load_flag p2 + pha ;use stack to load status + lda p1,x ;load to indexed zeropage + sta zpt,x + plp + .endmacro + + .macro set_abs p1,p2 ;precharging indexed memory & immediate status + load_flag p2 + pha ;use stack to load status + lda p1,x ;load to memory + sta abst + plp + .endmacro + + .macro set_absx p1,p2 ;precharging abs,x & immediate status + load_flag p2 + pha ;use stack to load status + lda p1,x ;load to indexed memory + sta abst,x + plp + .endmacro + +;macros to test (register|memory|zeropage) & status & (mask) + .macro tst_stat p1 ;testing flags in the processor status register + php ;save status + pla ;use stack to retrieve status + pha + cmp_flag p1 + trap_ne + plp ;restore status + .endmacro + + .macro tst_a p1,p2 ;testing result in accu & flags + php ;save flags + cmp #p1 ;test result + trap_ne + pla ;load status + pha + cmp_flag p2 + trap_ne + plp ;restore status + .endmacro + + .macro tst_x p1,p2 ;testing result in x index & flags + php ;save flags + cpx #p1 ;test result + trap_ne + pla ;load status + pha + cmp_flag p2 + trap_ne + plp ;restore status + .endmacro + + .macro tst_y p1,p2 ;testing result in y index & flags + php ;save flags + cpy #p1 ;test result + trap_ne + pla ;load status + pha + cmp_flag p2 + trap_ne + plp ;restore status + .endmacro + + .macro tst_ax p1,p2,p3 ;indexed testing result in accu & flags + php ;save flags + cmp p1,x ;test result + trap_ne + pla ;load status + eor_flag p3 + cmp p2,x ;test flags + trap_ne ; + .endmacro + + .macro tst_ay p1,p2,p3 ;indexed testing result in accu & flags + php ;save flags + cmp p1,y ;test result + trap_ne ; + pla ;load status + eor_flag p3 + cmp p2,y ;test flags + trap_ne + .endmacro + + .macro tst_z p1,p2,p3 ;indexed testing result in zp & flags + php ;save flags + lda zpt + cmp p1,x ;test result + trap_ne + pla ;load status + eor_flag p3 + cmp p2,x ;test flags + trap_ne + .endmacro + + .macro tst_zx p1,p2,p3 ;testing result in zp,x & flags + php ;save flags + lda zpt,x + cmp p1,x ;test result + trap_ne + pla ;load status + eor_flag p3 + cmp p2,x ;test flags + trap_ne + .endmacro + + .macro tst_abs p1,p2,p3 ;indexed testing result in memory & flags + php ;save flags + lda abst + cmp p1,x ;test result + trap_ne + pla ;load status + eor_flag p3 + cmp p2,x ;test flags + trap_ne + .endmacro + + .macro tst_absx p1,p2,p3 ;testing result in abs,x & flags + php ;save flags + lda abst,x + cmp p1,x ;test result + trap_ne + pla ;load status + eor_flag p3 + cmp p2,x ;test flags + trap_ne + .endmacro + +; RAM integrity test +; verifies that none of the previous tests has altered RAM outside of the +; designated write areas. +; uses zpt word as indirect pointer, zpt+2 word as checksum + .if ram_top > -1 +check_ram macro + cld + lda #0 + sta zpt ;set low byte of indirect pointer + sta zpt+3 ;checksum high byte + .if disable_selfmod = 0 + sta range_adr ;reset self modifying code + .endif + clc + ldx #zp_bss-zero_page ;zeropage - write test area +ccs3: adc zero_page,x + bcc ccs2 + inc zpt+3 ;carry to high byte + clc +ccs2: inx + bne ccs3 + ldx #hi(abs1) ;set high byte of indirect pointer + stx zpt+1 + ldy #lo(abs1) ;data after write & execute test area +ccs5: adc (zpt),y + bcc ccs4 + inc zpt+3 ;carry to high byte + clc +ccs4: iny + bne ccs5 + inx ;advance RAM high address + stx zpt+1 + cpx #ram_top + bne ccs5 + sta zpt+2 ;checksum low is + cmp ram_chksm ;checksum low expected + trap_ne ;checksum mismatch + lda zpt+3 ;checksum high is + cmp ram_chksm+1 ;checksum high expected + trap_ne ;checksum mismatch + .endmacro + .else + .macro check_ram + ;RAM check disabled - RAM size not set + .endmacro + .endif + + .macro next_test ;make sure, tests don't jump the fence + lda test_case ;previous test + cmp #test_num + trap_ne ;test is out of sequence +test_num .set test_num + 1 + lda #test_num ;*** next tests' number + sta test_case + ;check_ram ;uncomment to find altered RAM after each test + .endmacro + + .ZEROPAGE + .res zero_page, 0 + .org zero_page + +;break test interrupt save +irq_a: .res 1,0 ;a register +irq_x: .res 1,0 ;x register + .if I_flag = 2 +;masking for I bit in status +flag_I_on: .res 1,0 ;or mask to load flags +flag_I_off: .res 1,0 ;and mask to load flags + .endif +zpt: ;6 bytes store/modify test area +;add/subtract operand generation and result/flag prediction +adfc: .res 1,0 ;carry flag before op +ad1: .res 1,0 ;operand 1 - accumulator +ad2: .res 1,0 ;operand 2 - memory / immediate +adrl: .res 1,0 ;expected result bits 0-7 +adrh: .res 1,0 ;expected result bit 8 (carry) +adrf: .res 1,0 ;expected flags NV0000ZC (only binary mode) +sb2: .res 1,0 ;operand 2 complemented for subtract +zp_bss: +zps: .byte $80,1 ;additional shift pattern to test zero result & flag +zp1: .byte $c3,$82,$41,0 ;test patterns for LDx BIT ROL ROR ASL LSR +zp7f: .byte $7f ;test pattern for compare +;logical zeropage operands +zpOR: .byte 0,$1f,$71,$80 ;test pattern for OR +zpAN: .byte $0f,$ff,$7f,$80 ;test pattern for AND +zpEO: .byte $ff,$0f,$8f,$8f ;test pattern for EOR +;indirect addressing pointers +ind1: .word abs1 ;indirect pointer to pattern in absolute memory + .word abs1+1 + .word abs1+2 + .word abs1+3 + .word abs7f +inw1: .word abs1-$f8 ;indirect pointer for wrap-test pattern +indt: .word abst ;indirect pointer to store area in absolute memory + .word abst+1 + .word abst+2 + .word abst+3 +inwt: .word abst-$f8 ;indirect pointer for wrap-test store +indAN: .word absAN ;indirect pointer to AND pattern in absolute memory + .word absAN+1 + .word absAN+2 + .word absAN+3 +indEO: .word absEO ;indirect pointer to EOR pattern in absolute memory + .word absEO+1 + .word absEO+2 + .word absEO+3 +indOR: .word absOR ;indirect pointer to OR pattern in absolute memory + .word absOR+1 + .word absOR+2 + .word absOR+3 +;add/subtract indirect pointers +adi2: .word ada2 ;indirect pointer to operand 2 in absolute memory +sbi2: .word sba2 ;indirect pointer to complemented operand 2 (SBC) +adiy2: .word ada2-$ff ;with offset for indirect indexed +sbiy2: .word sba2-$ff +zp_bss_end: + + .DATA + .org data_segment + +test_case: .res 1,0 ;current test number +ram_chksm: .res 2,0 ;checksum for RAM integrity test +;add/subtract operand copy - abs tests write area +abst: ;6 bytes store/modify test area +ada2: .res 1,0 ;operand 2 +sba2: .res 1,0 ;operand 2 complemented for subtract + .res 4,0 ;fill remaining bytes +data_bss: + .if load_data_direct = 1 +ex_andi:and #0 ;execute immediate opcodes + rts +ex_eori:eor #0 ;execute immediate opcodes + rts +ex_orai:ora #0 ;execute immediate opcodes + rts +ex_adci:adc #0 ;execute immediate opcodes + rts +ex_sbci:sbc #0 ;execute immediate opcodes + rts + .else +ex_andi:.res 3 +ex_eori:.res 3 +ex_orai:.res 3 +ex_adci:.res 3 +ex_sbci:.res 3 + .endif +;zps .byte $80,1 ;additional shift patterns test zero result & flag +abs1: .byte $c3,$82,$41,0 ;test patterns for LDx BIT ROL ROR ASL LSR +abs7f: .byte $7f ;test pattern for compare +;loads +fLDx: .byte fn,fn,0,fz ;expected flags for load +;shifts +rASL: ;expected result ASL & ROL -carry +rROL: .byte 0,2,$86,$04,$82,0 +rROLc: .byte 1,3,$87,$05,$83,1 ;expected result ROL +carry +rLSR: ;expected result LSR & ROR -carry +rROR: .byte $40,0,$61,$41,$20,0 +rRORc: .byte $c0,$80,$e1,$c1,$a0,$80 ;expected result ROR +carry +fASL: ;expected flags for shifts +fROL: .byte fzc,0,fnc,fc,fn,fz ;no carry in +fROLc: .byte fc,0,fnc,fc,fn,0 ;carry in +fLSR: +fROR: .byte 0,fzc,fc,0,fc,fz ;no carry in +fRORc: .byte fn,fnc,fnc,fn,fnc,fn ;carry in +;increments (decrements) +rINC: .byte $7f,$80,$ff,0,1 ;expected result for INC/DEC +fINC: .byte 0,fn,fn,fz,0 ;expected flags for INC/DEC +;logical memory operand +absOR: .byte 0,$1f,$71,$80 ;test pattern for OR +absAN: .byte $0f,$ff,$7f,$80 ;test pattern for AND +absEO: .byte $ff,$0f,$8f,$8f ;test pattern for EOR +;logical accu operand +absORa: .byte 0,$f1,$1f,0 ;test pattern for OR +absANa: .byte $f0,$ff,$ff,$ff ;test pattern for AND +absEOa: .byte $ff,$f0,$f0,$0f ;test pattern for EOR +;logical results +absrlo: .byte 0,$ff,$7f,$80 +absflo: .byte fz,fn,0,fn +data_bss_end: + + + .CODE + .org code_segment + .P02 ; disable 65SC02, 65C02 and 65816 instructions +start: cld + ldx #$ff + txs + lda #0 ;*** test 0 = initialize + sta test_case +test_num .set 0 + +;stop interrupts before initializing BSS + .if I_flag = 1 + sei + .endif + +;initialize I/O for report channel + .if report = 1 + jsr report_init + .endif + +;pretest small branch offset + ldx #5 + jmp psb_test +psb_bwok: + ldy #5 + bne psb_forw + trap ;branch should be taken + dey ;forward landing zone + dey + dey + dey + dey +psb_forw: + dey + dey + dey + dey + dey + beq psb_fwok + trap ;forward offset + + dex ;backward landing zone + dex + dex + dex + dex +psb_back: + dex + dex + dex + dex + dex + beq psb_bwok + trap ;backward offset +psb_test: + bne psb_back + trap ;branch should be taken +psb_fwok: + +;initialize BSS segment + .if load_data_direct <> 1 + ldx #zp_end-zp_init-1 +ld_zp: lda zp_init,x + sta zp_bss,x + dex + bpl ld_zp + ldx #data_end-data_init-1 +ld_data:lda data_init,x + sta data_bss,x + dex + bpl ld_data + .if ROM_vectors = 1 + ldx #5 +ld_vect:lda vec_init,x + sta vec_bss,x + dex + bpl ld_vect + .endif + .endif + +;retain status of interrupt flag + .if I_flag = 2 + php + pla + and #4 ;isolate flag + sta flag_I_on ;or mask + eor #lo(~4) ;reverse + sta flag_I_off ;and mask + .endif + +;generate checksum for RAM integrity test + .if ram_top > -1 + lda #0 + sta zpt ;set low byte of indirect pointer + sta ram_chksm+1 ;checksum high byte + .if disable_selfmod = 0 + sta range_adr ;reset self modifying code + .endif + clc + ldx #zp_bss-zero_page ;zeropage - write test area +gcs3: adc zero_page,x + bcc gcs2 + inc ram_chksm+1 ;carry to high byte + clc +gcs2: inx + bne gcs3 + ldx #hi(abs1) ;set high byte of indirect pointer + stx zpt+1 + ldy #lo(abs1) ;data after write & execute test area +gcs5: adc (zpt),y + bcc gcs4 + inc ram_chksm+1 ;carry to high byte + clc +gcs4: iny + bne gcs5 + inx ;advance RAM high address + stx zpt+1 + cpx #ram_top + bne gcs5 + sta ram_chksm ;checksum complete + .endif + next_test + + .if disable_selfmod = 0 +;testing relative addressing with BEQ + ldy #$fe ;testing maximum range, not -1/-2 (invalid/self adr) +range_loop: + dey ;next relative address + tya + tax ;precharge count to end of loop + bpl range_fw ;calculate relative address + clc ;avoid branch self or to relative address of branch + adc #2 + nop ;offset landing zone - tolerate +/-5 offset to branch + nop + nop + nop + nop +range_fw: + nop + nop + nop + nop + nop + eor #$7f ;complement except sign + sta range_adr ;load into test target + lda #0 ;should set zero flag in status register + jmp range_op + + dex ; offset landing zone - backward branch too far + dex + dex + dex + dex + ;relative address target field with branch under test in the middle + dex ;-128 - max backward + dex + dex + dex + dex + dex + dex + dex + dex ;-120 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-110 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-100 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-90 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-80 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-70 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-60 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-50 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-40 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-30 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-20 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-10 + dex + dex + dex + dex + dex + dex + dex ;-3 +range_op: ;test target with zero flag=0, z=1 if previous dex +range_adr = *+1 ;modifiable relative address + beq *+64 ;+64 if called without modification + dex ;+0 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+10 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+20 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+30 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+40 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+50 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+60 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+70 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+80 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+90 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+100 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+110 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+120 + dex + dex + dex + dex + dex + dex + nop ;offset landing zone - forward branch too far + nop + nop + nop + nop + beq range_ok ;+127 - max forward + trap ; bad range + nop ;offset landing zone - tolerate +/-5 offset to branch + nop + nop + nop + nop +range_ok: + nop + nop + nop + nop + nop + cpy #0 + beq range_end + jmp range_loop +range_end: ;range test successful + .endif + next_test + +;partial test BNE & CMP, CPX, CPY immediate + cpy #1 ;testing BNE true + bne test_bne + trap +test_bne: + lda #0 + cmp #0 ;test compare immediate + trap_ne + trap_cc + trap_mi + cmp #1 + trap_eq + trap_cs + trap_pl + tax + cpx #0 ;test compare x immediate + trap_ne + trap_cc + trap_mi + cpx #1 + trap_eq + trap_cs + trap_pl + tay + cpy #0 ;test compare y immediate + trap_ne + trap_cc + trap_mi + cpy #1 + trap_eq + trap_cs + trap_pl + next_test +;testing stack operations PHA PHP PLA PLP + + ldx #$ff ;initialize stack + txs + lda #$55 + pha + lda #$aa + pha + cmp $1fe ;on stack ? + trap_ne + tsx + txa ;overwrite accu + cmp #$fd ;sp decremented? + trap_ne + pla + cmp #$aa ;successful retreived from stack? + trap_ne + pla + cmp #$55 + trap_ne + cmp $1ff ;remains on stack? + trap_ne + tsx + cpx #$ff ;sp incremented? + trap_ne + next_test + +;testing branch decisions BPL BMI BVC BVS BCC BCS BNE BEQ + set_stat $ff ;all on + bpl nbr1 ;branches should not be taken + bvc nbr2 + bcc nbr3 + bne nbr4 + bmi br1 ;branches should be taken + trap +br1: bvs br2 + trap +br2: bcs br3 + trap +br3: beq br4 + trap +nbr1: + trap ;previous bpl taken +nbr2: + trap ;previous bvc taken +nbr3: + trap ;previous bcc taken +nbr4: + trap ;previous bne taken +br4: php + tsx + cpx #$fe ;sp after php? + trap_ne + pla + cmp_flag $ff ;returned all flags on? + trap_ne + tsx + cpx #$ff ;sp after php? + trap_ne + set_stat 0 ;all off + bmi nbr11 ;branches should not be taken + bvs nbr12 + bcs nbr13 + beq nbr14 + bpl br11 ;branches should be taken + trap +br11: bvc br12 + trap +br12: bcc br13 + trap +br13: bne br14 + trap +nbr11: + trap ;previous bmi taken +nbr12: + trap ;previous bvs taken +nbr13: + trap ;previous bcs taken +nbr14: + trap ;previous beq taken +br14: php + pla + cmp_flag 0 ;flags off except break (pushed by sw) + reserved? + trap_ne + ;crosscheck flags + set_stat zero + bne brzs1 + beq brzs2 +brzs1: + trap ;branch zero/non zero +brzs2: bcs brzs3 + bcc brzs4 +brzs3: + trap ;branch carry/no carry +brzs4: bmi brzs5 + bpl brzs6 +brzs5: + trap ;branch minus/plus +brzs6: bvs brzs7 + bvc brzs8 +brzs7: + trap ;branch overflow/no overflow +brzs8: + set_stat carry + beq brcs1 + bne brcs2 +brcs1: + trap ;branch zero/non zero +brcs2: bcc brcs3 + bcs brcs4 +brcs3: + trap ;branch carry/no carry +brcs4: bmi brcs5 + bpl brcs6 +brcs5: + trap ;branch minus/plus +brcs6: bvs brcs7 + bvc brcs8 +brcs7: + trap ;branch overflow/no overflow + +brcs8: + set_stat minus + beq brmi1 + bne brmi2 +brmi1: + trap ;branch zero/non zero +brmi2: bcs brmi3 + bcc brmi4 +brmi3: + trap ;branch carry/no carry +brmi4: bpl brmi5 + bmi brmi6 +brmi5: + trap ;branch minus/plus +brmi6: bvs brmi7 + bvc brmi8 +brmi7: + trap ;branch overflow/no overflow +brmi8: + set_stat overfl + beq brvs1 + bne brvs2 +brvs1: + trap ;branch zero/non zero +brvs2: bcs brvs3 + bcc brvs4 +brvs3: + trap ;branch carry/no carry +brvs4: bmi brvs5 + bpl brvs6 +brvs5: + trap ;branch minus/plus +brvs6: bvc brvs7 + bvs brvs8 +brvs7: + trap ;branch overflow/no overflow +brvs8: + set_stat $ff-zero + beq brzc1 + bne brzc2 +brzc1: + trap ;branch zero/non zero +brzc2: bcc brzc3 + bcs brzc4 +brzc3: + trap ;branch carry/no carry +brzc4: bpl brzc5 + bmi brzc6 +brzc5: + trap ;branch minus/plus +brzc6: bvc brzc7 + bvs brzc8 +brzc7: + trap ;branch overflow/no overflow +brzc8: + set_stat $ff-carry + bne brcc1 + beq brcc2 +brcc1: + trap ;branch zero/non zero +brcc2: bcs brcc3 + bcc brcc4 +brcc3: + trap ;branch carry/no carry +brcc4: bpl brcc5 + bmi brcc6 +brcc5: + trap ;branch minus/plus +brcc6: bvc brcc7 + bvs brcc8 +brcc7: + trap ;branch overflow/no overflow +brcc8: + set_stat $ff-minus + bne brpl1 + beq brpl2 +brpl1: + trap ;branch zero/non zero +brpl2: bcc brpl3 + bcs brpl4 +brpl3: + trap ;branch carry/no carry +brpl4: bmi brpl5 + bpl brpl6 +brpl5: + trap ;branch minus/plus +brpl6: bvc brpl7 + bvs brpl8 +brpl7: + trap ;branch overflow/no overflow +brpl8: + set_stat $ff-overfl + bne brvc1 + beq brvc2 +brvc1: + trap ;branch zero/non zero +brvc2: bcc brvc3 + bcs brvc4 +brvc3: + trap ;branch carry/no carry +brvc4: bpl brvc5 + bmi brvc6 +brvc5: + trap ;branch minus/plus +brvc6: bvs brvc7 + bvc brvc8 +brvc7: + trap ;branch overflow/no overflow +brvc8: + next_test + +; test PHA does not alter flags or accumulator but PLA does + ldx #$55 ;x & y protected + ldy #$aa + set_a 1,$ff ;push + pha + tst_a 1,$ff + set_a 0,0 + pha + tst_a 0,0 + set_a $ff,$ff + pha + tst_a $ff,$ff + set_a 1,0 + pha + tst_a 1,0 + set_a 0,$ff + pha + tst_a 0,$ff + set_a $ff,0 + pha + tst_a $ff,0 + set_a 0,$ff ;pull + pla + tst_a $ff,$ff-zero + set_a $ff,0 + pla + tst_a 0,zero + set_a $fe,$ff + pla + tst_a 1,$ff-zero-minus + set_a 0,0 + pla + tst_a $ff,minus + set_a $ff,$ff + pla + tst_a 0,$ff-minus + set_a $fe,0 + pla + tst_a 1,0 + cpx #$55 ;x & y unchanged? + trap_ne + cpy #$aa + trap_ne + next_test + +; partial pretest EOR # + set_a $3c,0 + eor #$c3 + tst_a $ff,fn + set_a $c3,0 + eor #$c3 + tst_a 0,fz + next_test + +; PC modifying instructions except branches (NOP, JMP, JSR, RTS, BRK, RTI) +; testing NOP + ldx #$24 + ldy #$42 + set_a $18,0 + nop + tst_a $18,0 + cpx #$24 + trap_ne + cpy #$42 + trap_ne + ldx #$db + ldy #$bd + set_a $e7,$ff + nop + tst_a $e7,$ff + cpx #$db + trap_ne + cpy #$bd + trap_ne + next_test + +; jump absolute + set_stat $0 + lda #'F' + ldx #'A' + ldy #'R' ;N=0, V=0, Z=0, C=0 + jmp test_far + nop + nop + trap_ne ;runover protection + inx + inx +far_ret: + trap_eq ;returned flags OK? + trap_pl + trap_cc + trap_vc + cmp #('F'^$aa) ;returned registers OK? + trap_ne + cpx #('A'+1) + trap_ne + cpy #('R'-3) + trap_ne + dex + iny + iny + iny + eor #$aa ;N=0, V=1, Z=0, C=1 + jmp test_near + nop + nop + trap_ne ;runover protection + inx + inx +test_near: + trap_eq ;passed flags OK? + trap_mi + trap_cc + trap_vc + cmp #'F' ;passed registers OK? + trap_ne + cpx #'A' + trap_ne + cpy #'R' + trap_ne + next_test + +; jump indirect + set_stat 0 + lda #'I' + ldx #'N' + ldy #'D' ;N=0, V=0, Z=0, C=0 + jmp (ptr_tst_ind) + nop + trap_ne ;runover protection + dey + dey +ind_ret: + php ;either SP or Y count will fail, if we do not hit + dey + dey + dey + plp + trap_eq ;returned flags OK? + trap_pl + trap_cc + trap_vc + cmp #('I'^$aa) ;returned registers OK? + trap_ne + cpx #('N'+1) + trap_ne + cpy #('D'-6) + trap_ne + tsx ;SP check + cpx #$ff + trap_ne + next_test + +; jump subroutine & return from subroutine + set_stat 0 + lda #'J' + ldx #'S' + ldy #'R' ;N=0, V=0, Z=0, C=0 + jsr test_jsr +jsr_ret = *-1 ;last address of jsr = return address + php ;either SP or Y count will fail, if we do not hit + dey + dey + dey + plp + trap_eq ;returned flags OK? + trap_pl + trap_cc + trap_vc + cmp #('J'^$aa) ;returned registers OK? + trap_ne + cpx #('S'+1) + trap_ne + cpy #('R'-6) + trap_ne + tsx ;sp? + cpx #$ff + trap_ne + next_test + +; break & return from interrupt + .if ROM_vectors = 1 + load_flag 0 ;with interrupts enabled if allowed! + pha + lda #'B' + ldx #'R' + ldy #'K' + plp ;N=0, V=0, Z=0, C=0 + brk + .else + lda #>brk_ret0 ;emulated break + pha + lda #brk_ret1 ;emulated break + pha + lda #bin_rti_ret ;emulated interrupt for rti + pha + lda #dec_rti_ret ;emulated interrupt for rti + pha + lda #jsr_ret + trap_ne + lda $1fe + cmp #brk_ret0 + trap_ne + lda $1fe + cmp #brk_ret1 + trap_ne + lda $1fe + cmp # 1 +zp_init: +zps_: .byte $80,1 ;additional shift pattern to test zero result & flag +zp1_: .byte $c3,$82,$41,0 ;test patterns for LDx BIT ROL ROR ASL LSR +zp7f_: .byte $7f ;test pattern for compare +;logical zeropage operands +zpOR_: .byte 0,$1f,$71,$80 ;test pattern for OR +zpAN_: .byte $0f,$ff,$7f,$80 ;test pattern for AND +zpEO_: .byte $ff,$0f,$8f,$8f ;test pattern for EOR +;indirect addressing pointers +ind1_: .word abs1 ;indirect pointer to pattern in absolute memory + .word abs1+1 + .word abs1+2 + .word abs1+3 + .word abs7f +inw1_: .word abs1-$f8 ;indirect pointer for wrap-test pattern +indt_: .word abst ;indirect pointer to store area in absolute memory + .word abst+1 + .word abst+2 + .word abst+3 +inwt_: .word abst-$f8 ;indirect pointer for wrap-test store +indAN_: .word absAN ;indirect pointer to AND pattern in absolute memory + .word absAN+1 + .word absAN+2 + .word absAN+3 +indEO_: .word absEO ;indirect pointer to EOR pattern in absolute memory + .word absEO+1 + .word absEO+2 + .word absEO+3 +indOR_: .word absOR ;indirect pointer to OR pattern in absolute memory + .word absOR+1 + .word absOR+2 + .word absOR+3 +;add/subtract indirect pointers +adi2_: .word ada2 ;indirect pointer to operand 2 in absolute memory +sbi2_: .word sba2 ;indirect pointer to complemented operand 2 (SBC) +adiy2_: .word ada2-$ff ;with offset for indirect indexed +sbiy2_: .word sba2-$ff +zp_end: + .if (zp_end - zp_init) <> (zp_bss_end - zp_bss) + ;force assembler error if size is different + .error "mismatch between bss and zeropage data" + .endif +data_init: +ex_and_:and #0 ;execute immediate opcodes + rts +ex_eor_:eor #0 ;execute immediate opcodes + rts +ex_ora_:ora #0 ;execute immediate opcodes + rts +ex_adc_:adc #0 ;execute immediate opcodes + rts +ex_sbc_:sbc #0 ;execute immediate opcodes + rts +;zps: .byte $80,1 ;additional shift patterns test zero result & flag +abs1_: .byte $c3,$82,$41,0 ;test patterns for LDx BIT ROL ROR ASL LSR +abs7f_: .byte $7f ;test pattern for compare +;loads +fLDx_: .byte fn,fn,0,fz ;expected flags for load +;shifts +rASL_: ;expected result ASL & ROL -carry +rROL_: .byte 0,2,$86,$04,$82,0 +rROLc_: .byte 1,3,$87,$05,$83,1 ;expected result ROL +carry +rLSR_: ;expected result LSR & ROR -carry +rROR_: .byte $40,0,$61,$41,$20,0 +rRORc_: .byte $c0,$80,$e1,$c1,$a0,$80 ;expected result ROR +carry +fASL_: ;expected flags for shifts +fROL_: .byte fzc,0,fnc,fc,fn,fz ;no carry in +fROLc_: .byte fc,0,fnc,fc,fn,0 ;carry in +fLSR_: +fROR_: .byte 0,fzc,fc,0,fc,fz ;no carry in +fRORc_: .byte fn,fnc,fnc,fn,fnc,fn ;carry in +;increments (decrements) +rINC_: .byte $7f,$80,$ff,0,1 ;expected result for INC/DEC +fINC_: .byte 0,fn,fn,fz,0 ;expected flags for INC/DEC +;logical memory operand +absOR_: .byte 0,$1f,$71,$80 ;test pattern for OR +absAN_: .byte $0f,$ff,$7f,$80 ;test pattern for AND +absEO_: .byte $ff,$0f,$8f,$8f ;test pattern for EOR +;logical accu operand +absORa_:.byte 0,$f1,$1f,0 ;test pattern for OR +absANa_:.byte $f0,$ff,$ff,$ff ;test pattern for AND +absEOa_:.byte $ff,$f0,$f0,$0f ;test pattern for EOR +;logical results +absrlo_:.byte 0,$ff,$7f,$80 +absflo_:.byte fz,fn,0,fn +data_end + .if (data_end - data_init) <> (data_bss_end - data_bss) + ;force assembler error if size is different + .error "mismatch between bss and data" + .endif + +vec_init + .word nmi_trap + .word res_trap + .word irq_trap +vec_bss equ $fffa + .endif ;end of RAM init data + + .if (load_data_direct = 1) & (ROM_vectors = 1) + .segment "VECTORS" + .org $fffa ;vectors + .word nmi_trap + .word res_trap + .word irq_trap + .endif diff --git a/examples/asm/functional_test/Makefile b/examples/asm/functional_test/Makefile new file mode 100644 index 0000000..ff35eea --- /dev/null +++ b/examples/asm/functional_test/Makefile @@ -0,0 +1,3 @@ +build: + ca65 6502_functional_test.ca65 + ld65 -C ../linker.cfg -o 6502_functional_test.bin 6502_functional_test.o diff --git a/examples/asm/functional_test/README.md b/examples/asm/functional_test/README.md index 8b457be..907825d 100644 --- a/examples/asm/functional_test/README.md +++ b/examples/asm/functional_test/README.md @@ -17,6 +17,14 @@ make build This will create a `6502_functional_test.bin` file in the `build` directory, which the emulator will load. +## Running + +Then, from the root of the repository, run: + +```bash +cargo run --release --example functional +``` + ## Credits Taken from From d9499fa419ebdac4bdf970f08a26f8bb5a7a929a Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 28 Jun 2023 10:12:23 +0200 Subject: [PATCH 09/13] Use correct ca65 assembler file extension --- examples/asm/euclid/{euclid.a65 => euclid.ca65} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename examples/asm/euclid/{euclid.a65 => euclid.ca65} (94%) diff --git a/examples/asm/euclid/euclid.a65 b/examples/asm/euclid/euclid.ca65 similarity index 94% rename from examples/asm/euclid/euclid.a65 rename to examples/asm/euclid/euclid.ca65 index 150847c..8e678c9 100644 --- a/examples/asm/euclid/euclid.a65 +++ b/examples/asm/euclid/euclid.ca65 @@ -1,4 +1,4 @@ -; euclid.a65 +; euclid.ca65 ; A program to find the greatest common divisor of two numbers .ORG $1000 @@ -6,7 +6,7 @@ ; .algo LDA $00 ; Load from F to A ; .algo_ -sec ; Set carry flag +SEC ; Set carry flag SBC $01 ; Subtract S from the number in A (from F) BEQ end ; Jump to .end if the difference is zero BMI swap ; Jump to .swap if the difference is negative From 05420f23e50e003d5dcd2e8802417b13da138376 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 28 Jun 2023 10:41:38 +0200 Subject: [PATCH 10/13] Make instruction printable --- src/cpu.rs | 7 ++++--- src/instruction.rs | 21 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index 4ba4f90..e94d379 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -175,14 +175,15 @@ impl CPU { self.registers.program_counter = self.registers.program_counter.wrapping_add(num_bytes); - Some((instr, am_out)) + Some(DecodedInstr(instr, am_out)) } _ => None, } } - pub fn execute_instruction(&mut self, decoded_instr: DecodedInstr) { - match decoded_instr { + pub fn execute_instruction>(&mut self, decoded_instr: T) { + let decoded_instr = decoded_instr.into(); + match (decoded_instr.0, decoded_instr.1) { (Instruction::ADC, OpInput::UseImmediate(val)) => { debug!("add with carry immediate: {}", val); self.add_with_carry(val as i8); diff --git a/src/instruction.rs b/src/instruction.rs index c80396e..f3209d8 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -42,6 +42,8 @@ // PC | program counter // +use core::fmt::{Display, Error, Formatter}; + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Instruction { ADC, // ADd with Carry................ | NV ...ZC A = A + M + C @@ -152,7 +154,24 @@ impl AddressingMode { } } -pub type DecodedInstr = (Instruction, OpInput); +pub struct DecodedInstr(pub Instruction, pub OpInput); + +impl Display for DecodedInstr { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + match self.1 { + OpInput::UseImplied => write!(f, "{:?}", self.0), + OpInput::UseImmediate(v) => write!(f, "{:?} #${:02X}", self.0, v), + OpInput::UseRelative(v) => write!(f, "{:?} ${:04X}", self.0, v), + OpInput::UseAddress(v) => write!(f, "{:?} ${:04X}", self.0, v), + } + } +} + +impl From<(Instruction, OpInput)> for DecodedInstr { + fn from((instr, op): (Instruction, OpInput)) -> DecodedInstr { + DecodedInstr(instr, op) + } +} pub static OPCODES: [Option<(Instruction, AddressingMode)>; 256] = [ /*0x00*/ From 360e00da20cf334bc4898feeebae78bc7a4e028b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 28 Jun 2023 23:37:46 +0200 Subject: [PATCH 11/13] add debug info --- examples/asm/functional_test/Makefile | 4 +- examples/asm/functional_test/labels.dbg | 369 ++++++++++++++++++++++++ 2 files changed, 371 insertions(+), 2 deletions(-) create mode 100644 examples/asm/functional_test/labels.dbg diff --git a/examples/asm/functional_test/Makefile b/examples/asm/functional_test/Makefile index ff35eea..aed827e 100644 --- a/examples/asm/functional_test/Makefile +++ b/examples/asm/functional_test/Makefile @@ -1,3 +1,3 @@ build: - ca65 6502_functional_test.ca65 - ld65 -C ../linker.cfg -o 6502_functional_test.bin 6502_functional_test.o + ca65 -g 6502_functional_test.ca65 + ld65 -C ../linker.cfg -o 6502_functional_test.bin -Ln labels.dbg 6502_functional_test.o diff --git a/examples/asm/functional_test/labels.dbg b/examples/asm/functional_test/labels.dbg new file mode 100644 index 0000000..f2d1791 --- /dev/null +++ b/examples/asm/functional_test/labels.dbg @@ -0,0 +1,369 @@ +al 000000 .__RAM_FILEOFFS__ +al 00017B .__RAM_LAST__ +al 000200 .__RAM_SIZE__ +al 000100 .__RAM_START__ +al 000000 .__ZP_FILEOFFS__ +al 00005C .__ZP_LAST__ +al 000100 .__ZP_SIZE__ +al 000000 .__ZP_START__ +al 0036A7 .break2 +al 003655 .res_trap +al 00364F .nmi_trap +al 0035D2 .ptr_ind_ret +al 0035D9 .test_ind +al 00346A .ckad1 +al 0032FF .dec_rti_ret +al 003308 .bin_rti_ret +al 0032A4 .tdad6 +al 0032B8 .tdad7 +al 00327B .tdad4 +al 003280 .tdad5 +al 00325F .tdad2 +al 003261 .tdad3 +al 00324F .tdad1 +al 003322 .chkdad +al 00323D .tdad +al 0031EC .tadd1 +al 003455 .chkadd +al 0031DA .tadd +al 003195 .tora15 +al 003179 .tora14 +al 00315B .tora13 +al 00313B .tora12 +al 00311C .tora11 +al 0030FF .tora10 +al 0030E2 .tora9 +al 0030C5 .tora8 +al 0030A9 .tora7 +al 00308D .tora6 +al 00306B .tora5 +al 003049 .tora4 +al 003029 .tora3 +al 003009 .tora2 +al 002FE7 .tora1 +al 002FC5 .tora +al 002F9D .teor15 +al 002F81 .teor14 +al 002F63 .teor13 +al 002F43 .teor12 +al 002F24 .teor11 +al 002F07 .teor10 +al 002EEA .teor9 +al 002ECD .teor8 +al 002EB1 .teor7 +al 002E95 .teor6 +al 002E73 .teor5 +al 002E51 .teor4 +al 002E31 .teor3 +al 002E11 .teor2 +al 002DEF .teor1 +al 002DCD .teor +al 002DA5 .tand15 +al 002D89 .tand14 +al 002D6B .tand13 +al 002D4B .tand12 +al 002D2C .tand11 +al 002D0F .tand10 +al 002CF2 .tand9 +al 002CD5 .tand8 +al 002CB9 .tand7 +al 002C9D .tand6 +al 002C7B .tand5 +al 002C59 .tand4 +al 002C39 .tand3 +al 002C19 .tand2 +al 002BF7 .tand1 +al 002BD5 .tand +al 002BC7 .tdec17 +al 002B9E .tdec16 +al 002B97 .tinc17 +al 002B72 .tinc16 +al 002B6E .tdec7 +al 002B45 .tdec6 +al 002B3E .tinc7 +al 002B19 .tinc6 +al 002B09 .tdec15 +al 002AE4 .tdec14 +al 002ADD .tinc15 +al 002ABC .tinc14 +al 002AB8 .tdec5 +al 002A93 .tdec4 +al 002A8C .tinc5 +al 002A6B .tinc4 +al 002A5B .tdec13 +al 002A35 .tdec12 +al 002A2D .tinc13 +al 002A0B .tinc12 +al 002A04 .tdec3 +al 0029DE .tdec2 +al 0029D6 .tinc3 +al 0029B4 .tinc2 +al 0029A1 .tdec11 +al 00297E .tdec10 +al 002977 .tinc11 +al 002958 .tinc10 +al 002952 .tdec1 +al 00292F .tdec +al 002928 .tinc1 +al 002909 .tinc +al 0028D7 .trorc9 +al 0028B5 .trorc8 +al 002893 .tror9 +al 002871 .tror8 +al 00284F .trolc9 +al 00282D .trolc8 +al 00280B .trol9 +al 0027E9 .trol8 +al 0027C7 .tlsr9 +al 0027A5 .tlsr8 +al 002783 .tasl9 +al 002761 .tasl8 +al 002736 .trorc7 +al 002717 .trorc6 +al 0026F8 .tror7 +al 0026D9 .tror6 +al 0026BA .trolc7 +al 00269B .trolc6 +al 00267C .trol7 +al 00265D .trol6 +al 00263E .tlsr7 +al 00261F .tlsr6 +al 002600 .tasl7 +al 0025E1 .tasl6 +al 0025B3 .trorc5 +al 002591 .trorc4 +al 00256F .tror5 +al 00254D .tror4 +al 00252B .trolc5 +al 002509 .trolc4 +al 0024E7 .trol5 +al 0024C5 .trol4 +al 0024A3 .tlsr5 +al 002481 .tlsr4 +al 00245F .tasl5 +al 00243D .tasl4 +al 002412 .trorc3 +al 0023F3 .trorc2 +al 0023D4 .tror3 +al 0023B5 .tror2 +al 002396 .trolc3 +al 002377 .trolc2 +al 002358 .trol3 +al 002339 .trol2 +al 00231A .tlsr3 +al 0022FB .tlsr2 +al 0022DC .tasl3 +al 0022BD .tasl2 +al 002297 .trorc1 +al 00227D .trorc +al 002263 .tror1 +al 002249 .tror +al 00222F .trolc1 +al 002215 .trolc +al 0021FB .trol1 +al 0021E1 .trol +al 0021C7 .tlsr1 +al 0021AD .tlsr +al 002193 .tasl1 +al 002179 .tasl +al 00173E .tstay6 +al 00172F .tlday6 +al 00171C .tstay5 +al 00170E .tlday5 +al 0016FD .tstay4 +al 0016ED .tlday4 +al 0016D2 .tstax1 +al 0016C4 .tldax7 +al 0016B8 .tldax6 +al 001699 .tstay2 +al 001674 .tldax5 +al 00164F .tldax4 +al 00163A .tstay1 +al 001617 .tlday3 +al 0015F6 .tlday2 +al 0015E3 .tstay +al 0015BF .tlday1 +al 00159D .tlday +al 001574 .tstax +al 001551 .tldax3 +al 001530 .tldax2 +al 00150E .tldax1 +al 0014EC .tldax +al 000ED7 .tsty1 +al 000EC9 .tldy5 +al 000EBC .tldy4 +al 000E93 .tsty +al 000E6E .tldy3 +al 000E4B .tldy2 +al 000E28 .tldy1 +al 000E05 .tldy +al 000DDE .tstx1 +al 000DD0 .tldx5 +al 000DC3 .tldx4 +al 000D98 .tstx +al 000D72 .tldx3 +al 000D4E .tldx2 +al 000D2B .tldx1 +al 000D08 .tldx +al 0008CF .brk_ret1 +al 00365D .irq_trap +al 0008A0 .brk_ret0 +al 00360F .test_jsr +al 000828 .ind_ret +al 0035D0 .ptr_tst_ind +al 0007F6 .test_near +al 0007D3 .far_ret +al 0035A2 .test_far +al 00063A .brvc8 +al 000637 .brvc7 +al 000633 .brvc6 +al 000630 .brvc5 +al 00062C .brvc4 +al 000629 .brvc3 +al 000625 .brvc2 +al 000622 .brvc1 +al 00061A .brpl8 +al 000617 .brpl7 +al 000613 .brpl6 +al 000610 .brpl5 +al 00060C .brpl4 +al 000609 .brpl3 +al 000605 .brpl2 +al 000602 .brpl1 +al 0005FA .brcc8 +al 0005F7 .brcc7 +al 0005F3 .brcc6 +al 0005F0 .brcc5 +al 0005EC .brcc4 +al 0005E9 .brcc3 +al 0005E5 .brcc2 +al 0005E2 .brcc1 +al 0005DA .brzc8 +al 0005D7 .brzc7 +al 0005D3 .brzc6 +al 0005D0 .brzc5 +al 0005CC .brzc4 +al 0005C9 .brzc3 +al 0005C5 .brzc2 +al 0005C2 .brzc1 +al 0005BA .brvs8 +al 0005B7 .brvs7 +al 0005B3 .brvs6 +al 0005B0 .brvs5 +al 0005AC .brvs4 +al 0005A9 .brvs3 +al 0005A5 .brvs2 +al 0005A2 .brvs1 +al 00059A .brmi8 +al 000597 .brmi7 +al 000593 .brmi6 +al 000590 .brmi5 +al 00058C .brmi4 +al 000589 .brmi3 +al 000585 .brmi2 +al 000582 .brmi1 +al 00057A .brcs8 +al 000577 .brcs7 +al 000573 .brcs6 +al 000570 .brcs5 +al 00056C .brcs4 +al 000569 .brcs3 +al 000565 .brcs2 +al 000562 .brcs1 +al 00055A .brzs8 +al 000557 .brzs7 +al 000553 .brzs6 +al 000550 .brzs5 +al 00054C .brzs4 +al 000549 .brzs3 +al 000545 .brzs2 +al 000542 .brzs1 +al 000534 .br14 +al 000523 .br13 +al 00051E .br12 +al 000519 .br11 +al 000531 .nbr14 +al 00052E .nbr13 +al 00052B .nbr12 +al 000528 .nbr11 +al 0004F8 .br4 +al 0004E7 .br3 +al 0004E2 .br2 +al 0004DD .br1 +al 0004F5 .nbr4 +al 0004F2 .nbr3 +al 0004EF .nbr2 +al 0004EC .nbr1 +al 000458 .test_bne +al 00042A .psb_back +al 000439 .psb_fwok +al 00041B .psb_forw +al 00040F .psb_bwok +al 000434 .psb_test +al 000400 .start +al 00027B .data_bss_end +al 000277 .absflo +al 000273 .absrlo +al 00026F .absEOa +al 00026B .absANa +al 000267 .absORa +al 000256 .fINC +al 000251 .rINC +al 00024B .fRORc +al 000245 .fROR +al 000245 .fLSR +al 00023F .fROLc +al 000239 .fROL +al 000239 .fASL +al 000233 .rRORc +al 00022D .rROR +al 00022D .rLSR +al 000227 .rROLc +al 000221 .rROL +al 000221 .rASL +al 00021D .fLDx +al 000215 .ex_sbci +al 000212 .ex_adci +al 00020F .ex_orai +al 00020C .ex_eori +al 000209 .ex_andi +al 000209 .data_bss +al 000201 .ram_chksm +al 000200 .test_case +al 00005C .zp_bss_end +al 00005A .sbiy2 +al 000058 .adiy2 +al 000204 .sba2 +al 000056 .sbi2 +al 000203 .ada2 +al 000054 .adi2 +al 00025B .absOR +al 00004C .indOR +al 000263 .absEO +al 000044 .indEO +al 00025F .absAN +al 00003C .indAN +al 00003A .inwt +al 000203 .abst +al 000032 .indt +al 000030 .inw1 +al 00021C .abs7f +al 000218 .abs1 +al 000026 .ind1 +al 000022 .zpEO +al 00001E .zpAN +al 00001A .zpOR +al 000019 .zp7f +al 000015 .zp1 +al 000013 .zps +al 000013 .zp_bss +al 000012 .sb2 +al 000011 .adrf +al 000010 .adrh +al 00000F .adrl +al 00000E .ad2 +al 00000D .ad1 +al 00000C .adfc +al 00000C .zpt +al 00000B .irq_x +al 00000A .irq_a From 902b69a053ff0aa8b1e8517f2abee7e328c65807 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 28 Jun 2023 23:48:03 +0200 Subject: [PATCH 12/13] Use labels --- examples/functional.rs | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/examples/functional.rs b/examples/functional.rs index acea8ce..f177b89 100644 --- a/examples/functional.rs +++ b/examples/functional.rs @@ -1,7 +1,9 @@ use mos6502::cpu; use mos6502::memory::Bus; use mos6502::memory::Memory; -use std::fs::read; +use std::collections::HashMap; +use std::fs::{read, File}; +use std::io::{BufRead, BufReader}; fn main() { // Load the binary file from disk @@ -18,13 +20,20 @@ fn main() { cpu.memory.set_bytes(0x00, &program); cpu.registers.program_counter = 0x400; + let labels = + load_labels("examples/asm/functional_test/labels.dbg").expect("Could not load labels"); + // run step-by-step let mut old_pc = cpu.registers.program_counter; while cpu.registers.program_counter != 0x3468 { // Use `fetch_next_and_decode` instead of // `single_step` to see the decoded instruction if let Some(decoded_instr) = cpu.fetch_next_and_decode() { - println!("{decoded_instr}"); + let label = labels.get(&cpu.registers.program_counter); + match label { + Some(name) => println!("{}: {}", name, decoded_instr), + None => println!("{}", decoded_instr), + } cpu.execute_instruction(decoded_instr); } cpu.single_step(); @@ -38,3 +47,22 @@ fn main() { old_pc = cpu.registers.program_counter; } } + +fn load_labels(path: &str) -> std::io::Result> { + let mut labels = HashMap::new(); + let file = File::open(path)?; + let reader = BufReader::new(file); + + for line in reader.lines() { + let line = line?; + let parts: Vec<&str> = line.split(' ').collect(); + if parts.len() < 3 { + continue; + } + let address = u16::from_str_radix(parts[1], 16).unwrap(); + let label = parts[2].to_owned(); + labels.insert(address, label); + } + + Ok(labels) +} From efb1c3a3b8b41666278f62368585451234c412b3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 29 Jun 2023 10:45:11 +0200 Subject: [PATCH 13/13] Add Addressing mode to Display for DecodedInstr --- src/instruction.rs | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/instruction.rs b/src/instruction.rs index f3209d8..a382ae3 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -115,6 +115,17 @@ pub enum OpInput { UseAddress(u16), } +impl Display for OpInput { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + match self { + OpInput::UseImplied => write!(f, ""), + OpInput::UseImmediate(v) => write!(f, "#${:02X}", v), + OpInput::UseRelative(v) => write!(f, "${:04X}", v), + OpInput::UseAddress(v) => write!(f, "${:04X}", v), + } + } +} + #[derive(Copy, Clone)] pub enum AddressingMode { Accumulator, // 1 LSR A work directly on accumulator @@ -158,11 +169,33 @@ pub struct DecodedInstr(pub Instruction, pub OpInput); impl Display for DecodedInstr { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + // get addressing mode from instruction (if it exists) + let am: Option = OPCODES.get(self.0 as usize).and_then(|x| x.map(|x| x.1)); + match self.1 { OpInput::UseImplied => write!(f, "{:?}", self.0), - OpInput::UseImmediate(v) => write!(f, "{:?} #${:02X}", self.0, v), - OpInput::UseRelative(v) => write!(f, "{:?} ${:04X}", self.0, v), - OpInput::UseAddress(v) => write!(f, "{:?} ${:04X}", self.0, v), + OpInput::UseImmediate(imm) => write!(f, "{:?} #${:02X}", self.0, imm), + OpInput::UseRelative(rel) => write!(f, "{:?} ${:04X}", self.0, rel), + OpInput::UseAddress(addr) => match am { + Some(AddressingMode::Accumulator) => write!(f, "{:?} A", self.0), + Some(AddressingMode::Implied) => write!(f, "{:?}", self.0), + Some(AddressingMode::Immediate) => write!(f, "{:?} #${:02X}", self.0, addr), + Some(AddressingMode::ZeroPage) => write!(f, "{:?} :?${:02X}", self.0, addr), + Some(AddressingMode::ZeroPageX) => write!(f, "{:?} :?${:02X},X", self.0, addr), + Some(AddressingMode::ZeroPageY) => write!(f, "{:?} :?${:02X},Y", self.0, addr), + Some(AddressingMode::Relative) => write!(f, "{:?} ${:04X}", self.0, addr), + Some(AddressingMode::Absolute) => write!(f, "{:?} ${:04X}", self.0, addr), + Some(AddressingMode::AbsoluteX) => write!(f, "{:?} ${:04X},X", self.0, addr), + Some(AddressingMode::AbsoluteY) => write!(f, "{:?} ${:04X},Y", self.0, addr), + Some(AddressingMode::Indirect) => write!(f, "{:?} (${:04X})", self.0, addr), + Some(AddressingMode::IndexedIndirectX) => { + write!(f, "{:?} (${:04X},X)", self.0, addr) + } + Some(AddressingMode::IndirectIndexedY) => { + write!(f, "{:?} (${:04X}),Y", self.0, addr) + } + None => write!(f, "{:?} {}", self.0, self.1), + }, } } }