gameboy-rust/crates/gba-core/tests/flash_write.rs

157 lines
5.6 KiB
Rust

//! Original byte-program/erase probes. Nominal delays are not physical chip timings.
use gba_core::{
cartridge::{
SaveDevice, FLASH_ERASE_CYCLES as ERASE, FLASH_PROGRAM_CYCLES as PROGRAM,
SAVE_START as BASE,
},
io::{HALTCNT, IF},
memory::Memory,
};
const HI: u32 = BASE + 0x5555;
const LO: u32 = BASE + 0x2aaa;
fn bus(device: SaveDevice) -> Memory {
let mut m = Memory::new(vec![]).unwrap();
m.set_save_device(device);
m
}
fn command(m: &mut Memory, command: u8) {
m.write8(HI, 0xaa).unwrap();
m.write8(LO, 0x55).unwrap();
m.write8(HI, command).unwrap();
}
fn program(m: &mut Memory, address: u32, value: u8) {
command(m, 0xa0);
m.write8(address, value).unwrap();
}
fn erase(m: &mut Memory, address: u32, confirm: u8) {
command(m, 0x80);
m.write8(HI, 0xaa).unwrap();
m.write8(LO, 0x55).unwrap();
m.write8(address, confirm).unwrap();
}
fn bank(m: &mut Memory, index: u8) {
command(m, 0xb0);
m.write8(BASE, index).unwrap();
}
#[test]
fn every_byte_value_programs_after_delay_without_touching_neighbors_or_other_bank() {
let mut m = bus(SaveDevice::Flash128);
for b in 0..2 {
bank(&mut m, b);
for value in 0..=255 {
let address = BASE + u32::from(value);
program(&mut m, address, value);
assert!(m.save_write_pending());
assert_eq!(m.read8(address).unwrap(), (value ^ 0x80) & 0x80);
assert_eq!(
m.save_image().unwrap()[usize::from(b) * 65536 + usize::from(value)],
255
);
m.advance_cycles(PROGRAM - 1);
assert_eq!(m.read8(address).unwrap(), (value ^ 0x80) & 0x80);
m.advance_cycles(1);
assert_eq!(m.read8(address).unwrap(), value);
assert!(!m.save_write_pending());
}
}
let image = m.save_image().unwrap();
for b in 0..2 {
for (i, &v) in image[b * 65536..(b + 1) * 65536].iter().enumerate() {
assert_eq!(v, if i < 256 { i as u8 } else { 255 });
}
}
assert!(m.save_modified());
assert_eq!(m.read16(IF).unwrap(), 0);
}
#[test]
fn sector_and_chip_erases_have_exact_scope_and_wait_for_completion() {
for device in [SaveDevice::Flash64, SaveDevice::Flash128] {
let mut m = bus(device);
m.load_save_image(&vec![0; device.capacity()]).unwrap();
if device == SaveDevice::Flash128 {
bank(&mut m, 1);
}
erase(&mut m, BASE + 0xf000, 0x30);
assert_eq!(m.read8(BASE + 0xf000).unwrap(), 0);
m.advance_cycles(ERASE - 1);
assert!(m.save_image().unwrap().iter().all(|&b| b == 0));
m.advance_cycles(1);
let start = device.capacity() - 4096;
assert!(m.save_image().unwrap()[..start].iter().all(|&b| b == 0));
assert!(m.save_image().unwrap()[start..].iter().all(|&b| b == 255));
erase(&mut m, HI, 0x10);
m.advance_cycles(ERASE);
assert!(m.save_image().unwrap().iter().all(|&b| b == 255));
}
}
#[test]
fn data_at_unlock_address_is_not_mistaken_for_reset_and_zero_to_one_requires_erase() {
let mut m = bus(SaveDevice::Flash64);
program(&mut m, HI, 0xf0);
m.advance_cycles(PROGRAM);
assert_eq!(m.read8(HI).unwrap(), 0xf0);
program(&mut m, HI, 0x80);
m.advance_cycles(PROGRAM);
assert_eq!(m.read8(HI).unwrap(), 0x80);
command(&mut m, 0xa0);
let error = m.write8(HI, 0xff).unwrap_err();
assert!(error.to_string().contains("requires erase"));
assert_eq!(m.write8(HI, 0xff), Err(error));
m.write8(HI, 0).unwrap();
m.advance_cycles(PROGRAM);
assert_eq!(m.read8(HI).unwrap(), 0);
}
#[test]
fn busy_access_limits_and_64_reset_are_atomic_without_fabricated_completion() {
for device in [SaveDevice::Flash64, SaveDevice::Flash128] {
let mut m = bus(device);
program(&mut m, BASE + 123, 0x12);
assert!(m.read8(BASE + 124).is_err());
let error = m.write8(HI, 0xaa).unwrap_err();
assert_eq!(m.write8(HI, 0xaa), Err(error));
if device == SaveDevice::Flash64 {
m.write8(HI, 0xf0).unwrap();
m.advance_cycles(PROGRAM);
assert_eq!(m.read8(BASE + 123).unwrap(), 255);
assert!(!m.save_modified());
} else {
assert!(m.write8(HI, 0xf0).is_err());
m.advance_cycles(PROGRAM);
assert_eq!(m.read8(BASE + 123).unwrap(), 0x12);
}
}
}
#[test]
fn invalid_erase_sequences_preserve_data_and_can_be_retried() {
let mut m = bus(SaveDevice::Flash128);
m.load_save_image(&vec![0; 131072]).unwrap();
command(&mut m, 0x80);
assert!(m.write8(LO, 0x55).is_err());
m.write8(HI, 0xaa).unwrap();
m.write8(LO, 0x55).unwrap();
for (at, val) in [(BASE + 1, 0x30), (BASE, 0x10), (HI, 0x90)] {
let err = m.write8(at, val).unwrap_err();
assert_eq!(m.write8(at, val), Err(err));
assert!(m.save_image().unwrap().iter().all(|&b| b == 0));
}
m.write8(BASE, 0x30).unwrap();
m.advance_cycles(ERASE);
assert!(m.save_image().unwrap()[..4096].iter().all(|&b| b == 255));
}
#[test]
fn halt_clocks_nominal_operations_stop_freezes_them_and_setup_resets_dirty_state() {
let mut m = bus(SaveDevice::Flash128);
program(&mut m, BASE, 0);
m.write8(HALTCNT, 0).unwrap();
m.advance_cycles(PROGRAM);
assert!(m.save_modified());
assert_eq!(m.read8(BASE).unwrap(), 0);
m.load_save_image(&vec![255; 131072]).unwrap();
assert!(!m.save_modified());
program(&mut m, BASE, 1);
m.write8(HALTCNT, 0x80).unwrap();
m.advance_cycles(PROGRAM);
assert!(m.save_write_pending());
assert!(!m.save_modified());
assert_eq!(m.read8(BASE).unwrap(), 0x80);
}