mirror of
https://github.com/henrikpersson/potatis.git
synced 2025-04-02 10:32:09 -04:00
nestest went from 5s to ~1.9s by: - removing some dyn dispatch - const instruction map - opcode unrolling - removing uneccessary reads
180 lines
4.9 KiB
Rust
180 lines
4.9 KiB
Rust
use mos6502::memory::Bus;
|
|
use nes::nes::Nes;
|
|
|
|
mod common;
|
|
|
|
const STATUS_RUNNING: u8 = 0x80;
|
|
const STATUS_NEEDS_RESET: u8 = 0x81;
|
|
const STATUS_SUCCESS: u8 = 0x00;
|
|
const VALID_MAGIC: [u8; 3] = [0xde, 0xb0, 0x61];
|
|
|
|
#[derive(PartialEq, Eq)]
|
|
enum PassCond {
|
|
Status(&'static str, u8),
|
|
Pc(u16),
|
|
}
|
|
|
|
#[test]
|
|
fn instr_test_v5_official_mmc1() {
|
|
run_blargg_test(
|
|
"instr_test-v5/official_only.nes",
|
|
PassCond::Status("All 16 tests passed", STATUS_SUCCESS),
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn instr_test_v5_basic_nrom() {
|
|
// run_blargg_test("instr_test-v5/rom_singles/01-basics.nes", PassCond::Status("01-basics\n\nPassed", STATUS_SUCCESS));
|
|
run_blargg_test(
|
|
"instr_test-v5/rom_singles/01-basics.nes",
|
|
PassCond::Pc(0x01e2),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn instr_misc() {
|
|
// let success = "LDA abs,x\n\n03-dummy_reads\n\nFailed #3\n\nWhile running test 3 of 4";
|
|
let success = "Test requires $2002 mirroring every 8 bytes to $3FFA\n\n03-dummy_reads\n\nFailed #2\n\nWhile running test 3 of 4";
|
|
run_blargg_test("instr_misc/instr_misc.nes", PassCond::Status(success, 1));
|
|
}
|
|
|
|
#[ignore = "bad test"]
|
|
#[test]
|
|
fn ppu_vbl_nmi() {
|
|
let success = "VBL period is too long with BG off\n\n01-vbl_basics\n\nFailed #8";
|
|
// let success = "$2002 should be mirrored at $200A\n\n01-vbl_basics\n\nFailed #5";
|
|
run_blargg_test(
|
|
"ppu_vbl_nmi/rom_singles/01-vbl_basics.nes",
|
|
PassCond::Status(success, 0x08),
|
|
);
|
|
}
|
|
|
|
#[ignore = "not implemented"]
|
|
#[test]
|
|
fn ppu_open_bus() {
|
|
let success =
|
|
"Bits 2-4 of sprite attributes should always be clear when read\n\nppu_open_bus\n\nFailed #10";
|
|
run_blargg_test(
|
|
"ppu_open_bus/ppu_open_bus.nes",
|
|
PassCond::Status(success, 0x0a),
|
|
);
|
|
}
|
|
|
|
#[ignore = "bad test"]
|
|
#[test]
|
|
fn cpu_exec_space() {
|
|
let success = "\u{1b}[0;37mTEST:test_cpu_exec_space_ppuio\n\u{1b}[0;33mThis program verifies that the\nCPU can execute code from any\npossible location that it can\naddress, including I/O space.\n\nIn addition, it will be tested\nthat an RTS instruction does a\ndummy read of the byte that\nimmediately follows the\ninstructions.\n\n\u{1b}[0;37m\u{1b}[1;34mJSR+RTS TEST OK\nJMP+RTS TEST OK\nRTS+RTS TEST OK\nJMP+RTI TEST OK\nJMP+BRK TEST OK\n\u{1b}[0;37m\nPassed";
|
|
run_blargg_test(
|
|
"cpu_exec_space/test_cpu_exec_space_ppuio.nes",
|
|
PassCond::Status(success, 0x00),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn branch_timing() {
|
|
run_blargg_test(
|
|
"branch_timing_tests/1.Branch_Basics.nes",
|
|
PassCond::Pc(0xe01d),
|
|
);
|
|
run_blargg_test(
|
|
"branch_timing_tests/2.Backward_Branch.nes",
|
|
PassCond::Pc(0xe01d),
|
|
);
|
|
run_blargg_test(
|
|
"branch_timing_tests/3.Forward_Branch.nes",
|
|
PassCond::Pc(0xe01d),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn palette_ram() {
|
|
run_blargg_test(
|
|
"blargg_ppu_tests_2005.09.15b/palette_ram.nes",
|
|
PassCond::Pc(0xe0eb),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "bad test"]
|
|
fn oven_odd_frames() {
|
|
run_blargg_test(
|
|
"ppu_vbl_nmi/rom_singles/09-even_odd_frames.nes",
|
|
PassCond::Status("dunno", STATUS_SUCCESS),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "bad test"]
|
|
fn ppu_read_buffer() {
|
|
run_blargg_test(
|
|
"ppu_read_buffer/test_ppu_read_buffer.nes",
|
|
PassCond::Status("dunno", STATUS_SUCCESS),
|
|
);
|
|
}
|
|
|
|
fn run_blargg_test(test: &str, pass_condition: PassCond) {
|
|
let path = format!("../test-roms/nes-test-roms/{}", test);
|
|
let mut nes = common::setup(path.into(), std::env::var("VERBOSE").is_ok());
|
|
|
|
nes.debugger().verbose(true);
|
|
|
|
let result: String;
|
|
let mut status: Option<u8> = None;
|
|
|
|
// nes.debugger().watch_memory(0x3ffa, |val| {
|
|
// println!("3ffa: {:#04x}", val);
|
|
// });
|
|
|
|
nes
|
|
.debugger()
|
|
.watch_memory_range(0x6004..=0x6004 + 100, |mem| {
|
|
println!("{}", read_null_terminated_string(&mem));
|
|
});
|
|
|
|
loop {
|
|
nes.tick();
|
|
|
|
if PassCond::Pc(nes.cpu().pc) == pass_condition {
|
|
println!("success! pc at: {:#06x}", nes.cpu().pc);
|
|
return;
|
|
}
|
|
|
|
if check_and_update_status(&nes, &mut status) {
|
|
match status {
|
|
Some(STATUS_RUNNING) => (),
|
|
Some(STATUS_NEEDS_RESET) => panic!("needs reset.."),
|
|
Some(0x00..=0x7F) => {
|
|
// Completed, status is the result code.
|
|
let mem_view = nes.bus().read_range(0x6004..=0x6004 + 1000);
|
|
result = read_null_terminated_string(&mem_view);
|
|
break;
|
|
}
|
|
_ => panic!("unknown status"),
|
|
}
|
|
}
|
|
}
|
|
|
|
if let PassCond::Status(success_str, success_status) = pass_condition {
|
|
println!("status code: {:#04x}", status.unwrap());
|
|
assert_eq!(success_str, result.trim());
|
|
assert_eq!(Some(success_status), status);
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
}
|
|
|
|
fn read_null_terminated_string(range: &[u8]) -> String {
|
|
let string: Vec<u8> = range.iter().take_while(|&b| *b != 0x00).cloned().collect();
|
|
String::from_utf8(string).unwrap()
|
|
}
|
|
|
|
fn check_and_update_status(nes: &Nes, current_status: &mut Option<u8>) -> bool {
|
|
if nes.cpu().bus.read_range(0x6001..=0x6003) == VALID_MAGIC {
|
|
let new_status = nes.cpu().bus.read8(0x6000);
|
|
if Some(new_status) != *current_status {
|
|
*current_status = Some(new_status);
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|