677 lines
23 KiB
Rust
677 lines
23 KiB
Rust
use gba_core::{
|
|
bios::{self, IRQ_FLAGS, IRQ_HANDLER, SYSTEM_STACK, UNSUPPORTED_TRAP},
|
|
cpu::{CpuError, InstructionSet, Mode},
|
|
display::VBLANK_START,
|
|
io::{DISPSTAT, IE, IF, IME, POSTFLG, TIMER_BASE},
|
|
machine::{Machine, MachineError, StepKind},
|
|
memory::{Memory, MemoryError, BIOS_SIZE, ROM_START, VRAM_START},
|
|
};
|
|
|
|
const SOURCE: u32 = 0x0200_0000;
|
|
const DEST: u32 = 0x0201_0000;
|
|
const CALLBACK: u32 = 0x0300_0100;
|
|
const ROM_DATA: u32 = ROM_START + 13 * 4;
|
|
|
|
#[path = "bios/affine.rs"]
|
|
mod affine;
|
|
#[path = "bios/angles.rs"]
|
|
mod angles;
|
|
#[path = "bios/arithmetic.rs"]
|
|
mod arithmetic;
|
|
#[path = "bios/bit_unpack.rs"]
|
|
mod bit_unpack;
|
|
#[path = "bios/boot_video.rs"]
|
|
mod boot_video;
|
|
#[path = "bios/differential.rs"]
|
|
mod differential;
|
|
#[path = "bios/huffman.rs"]
|
|
mod huffman;
|
|
#[path = "bios/lz77.rs"]
|
|
mod lz77;
|
|
#[path = "bios/ram_reset.rs"]
|
|
mod ram_reset;
|
|
#[path = "bios/readback.rs"]
|
|
mod readback;
|
|
#[path = "bios/reset.rs"]
|
|
mod reset;
|
|
#[path = "bios/run_length.rs"]
|
|
mod run_length;
|
|
#[path = "bios/stop.rs"]
|
|
mod stop;
|
|
|
|
fn words(code: &[u32]) -> Vec<u8> {
|
|
code.iter().flat_map(|word| word.to_le_bytes()).collect()
|
|
}
|
|
|
|
fn reach(machine: &mut Machine, pc: u32, max_steps: usize) {
|
|
for _ in 0..max_steps {
|
|
if machine.cpu().pc() == pc {
|
|
return;
|
|
}
|
|
machine.step().unwrap();
|
|
}
|
|
panic!(
|
|
"did not reach {pc:#x}; pc={:#x}, cpsr={:#x}",
|
|
machine.cpu().pc(),
|
|
machine.cpu().cpsr()
|
|
);
|
|
}
|
|
|
|
/// Boot, load operands and status through CPU instructions, then stop just before SWI.
|
|
fn call(service: u8, thumb: bool, operands: [u32; 3]) -> (Machine, u32) {
|
|
call_status(service, thumb, operands, 0x1f)
|
|
}
|
|
|
|
fn call_status(service: u8, thumb: bool, operands: [u32; 3], status: u32) -> (Machine, u32) {
|
|
call_status_data(service, thumb, operands, status, &[])
|
|
}
|
|
|
|
fn call_status_data(
|
|
service: u8,
|
|
thumb: bool,
|
|
operands: [u32; 3],
|
|
status: u32,
|
|
data: &[u8],
|
|
) -> (Machine, u32) {
|
|
let mut code = [
|
|
0xe59f_0020, // LDR r0,[pc,#32] (offset 40)
|
|
0xe59f_1020,
|
|
0xe59f_2020,
|
|
0xe3a0_30a3, // MOV r3,#0xa3
|
|
0xe3a0_c05c, // MOV r12,#0x5c
|
|
0xe328_f480, // MSR CPSR_f,#0x80000000
|
|
if thumb { 0xe28f_e001 } else { 0xe1a0_e00f }, // ADD lr,pc,#1 / MOV lr,pc
|
|
if thumb { 0xe12f_ff1e } else { 0xe1a0_0000 }, // BX lr / NOP
|
|
if thumb {
|
|
0xe7fe_0000 | 0xdf00 | u32::from(service)
|
|
} else {
|
|
0xef00_0000 | u32::from(service) << 16
|
|
},
|
|
0xeaff_fffe,
|
|
operands[0],
|
|
operands[1],
|
|
operands[2],
|
|
];
|
|
if status != 0x1f {
|
|
code[3] = 0xe321_f000 | status;
|
|
} // MSR CPSR_c,#status
|
|
let mut rom = words(&code);
|
|
assert_eq!(rom.len() as u32, ROM_DATA - ROM_START);
|
|
rom.extend_from_slice(data);
|
|
let mut machine = bios::boot(rom).unwrap();
|
|
reach(&mut machine, ROM_START + 32, 100);
|
|
assert_eq!(
|
|
machine.cpu().instruction_set(),
|
|
if thumb {
|
|
InstructionSet::Thumb
|
|
} else {
|
|
InstructionSet::Arm
|
|
}
|
|
);
|
|
(machine, ROM_START + if thumb { 34 } else { 36 })
|
|
}
|
|
|
|
fn finish(machine: &mut Machine, return_pc: u32) {
|
|
machine.step().unwrap(); // Execute SWI, not a host service call.
|
|
reach(machine, return_pc, 100_000);
|
|
}
|
|
|
|
/// Original ARM callback: acknowledge IF, record BIOS flags, then return to dispatcher.
|
|
fn callback(bus: &mut Memory) {
|
|
let code = [
|
|
0xe280_0c02, // ADD r0,r0,#0x200 (r0=I/O base from dispatcher)
|
|
0xe1d0_10b2, // LDRH r1,[r0,#2]
|
|
0xe1c0_10b2, // STRH r1,[r0,#2]
|
|
0xe3a0_0403, // MOV r0,#0x03000000
|
|
0xe280_0c7f, // ADD r0,r0,#0x7f00
|
|
0xe1d0_2fb8, // LDRH r2,[r0,#0xf8]
|
|
0xe182_2001, // ORR r2,r2,r1
|
|
0xe1c0_2fb8, // STRH r2,[r0,#0xf8]
|
|
0xe12f_ff1e, // BX lr
|
|
];
|
|
for (index, instruction) in code.into_iter().enumerate() {
|
|
bus.write32(CALLBACK + index as u32 * 4, instruction)
|
|
.unwrap();
|
|
}
|
|
bus.write32(IRQ_HANDLER, CALLBACK).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn original_image_is_deterministic_and_boot_enters_arm_system_mode() {
|
|
assert_eq!(bios::image().len(), BIOS_SIZE);
|
|
assert_eq!(bios::image(), bios::image());
|
|
let mut machine = bios::boot(words(&[0xeaff_fffe])).unwrap();
|
|
assert_eq!(machine.cpu().mode(), Mode::Supervisor);
|
|
assert_eq!(machine.cpu().pc(), 0);
|
|
reach(&mut machine, ROM_START, 100);
|
|
assert_eq!(machine.cpu().mode(), Mode::System);
|
|
assert_eq!(machine.cpu().instruction_set(), InstructionSet::Arm);
|
|
assert_eq!(machine.cpu().cpsr(), 0x1f);
|
|
assert_eq!(machine.cpu().registers()[13], SYSTEM_STACK);
|
|
assert_eq!(machine.memory().read8(POSTFLG).unwrap(), 1);
|
|
assert_eq!(machine.memory().read32(IRQ_FLAGS).unwrap(), 0);
|
|
assert_eq!(machine.memory().read32(IRQ_HANDLER).unwrap(), 0);
|
|
assert!(machine.cycles() > 0);
|
|
}
|
|
|
|
#[test]
|
|
fn cpu_set_copies_both_widths_from_arm_and_thumb_and_preserves_caller() {
|
|
for thumb in [false, true] {
|
|
for word in [false, true] {
|
|
let (mut machine, return_pc) = call(
|
|
0x0b,
|
|
thumb,
|
|
[SOURCE, DEST, 4 | if word { 1 << 26 } else { 0 }],
|
|
);
|
|
for index in 0..4 {
|
|
machine
|
|
.memory_mut()
|
|
.write32(SOURCE + index * 4, 0x1234_5678 + index)
|
|
.unwrap();
|
|
}
|
|
let before = machine.cpu().clone();
|
|
finish(&mut machine, return_pc);
|
|
assert_eq!(&machine.cpu().registers()[..15], &before.registers()[..15]);
|
|
assert_eq!(machine.cpu().cpsr(), before.cpsr());
|
|
for offset in 0..if word { 16 } else { 8 } {
|
|
assert_eq!(
|
|
machine.memory().read8(DEST + offset).unwrap(),
|
|
machine.memory().read8(SOURCE + offset).unwrap()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn intr_wait_consumes_preexisting_selected_flags_without_sleep() {
|
|
for thumb in [false, true] {
|
|
let (mut machine, return_pc) = call(4, thumb, [0, 0x10, 0x1234]);
|
|
machine.memory_mut().write16(IRQ_FLAGS, 0x19).unwrap();
|
|
let before = machine.cpu().clone();
|
|
machine.step().unwrap();
|
|
for _ in 0..100 {
|
|
if machine.cpu().pc() == return_pc {
|
|
break;
|
|
}
|
|
assert_ne!(machine.step().unwrap(), StepKind::HaltIdle);
|
|
}
|
|
assert_eq!(machine.cpu().pc(), return_pc);
|
|
assert_eq!(machine.memory().read16(IRQ_FLAGS).unwrap(), 9);
|
|
assert_eq!(machine.memory().read16(IME).unwrap(), 1);
|
|
assert_eq!(machine.cpu().cpsr(), before.cpsr());
|
|
assert_eq!(&machine.cpu().registers()[..15], &before.registers()[..15]);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn vblank_wait_halts_dispatches_irq_and_returns_through_original_code() {
|
|
let (mut machine, return_pc) = call(5, false, [0x123, 0x456, 0x789]);
|
|
callback(machine.memory_mut());
|
|
machine.memory_mut().write16(DISPSTAT, 8).unwrap();
|
|
machine.memory_mut().write16(IE, 1).unwrap();
|
|
let before = machine.cpu().clone();
|
|
machine.step().unwrap();
|
|
let mut slept = false;
|
|
let mut irq = 0;
|
|
for _ in 0..1000 {
|
|
if machine.cpu().pc() == return_pc {
|
|
break;
|
|
}
|
|
match machine.step().unwrap() {
|
|
StepKind::HaltIdle => slept = true,
|
|
StepKind::IrqEntry => irq += 1,
|
|
_ => {}
|
|
}
|
|
}
|
|
assert!(slept);
|
|
assert_eq!(irq, 1);
|
|
assert_eq!(machine.cpu().pc(), return_pc);
|
|
assert_eq!(machine.cpu().cpsr(), before.cpsr());
|
|
assert_eq!(&machine.cpu().registers()[..15], &before.registers()[..15]);
|
|
assert_eq!(machine.memory().read16(IF).unwrap(), 0);
|
|
assert_eq!(machine.memory().read16(IRQ_FLAGS).unwrap(), 0);
|
|
assert_eq!(machine.memory().read16(IME).unwrap(), 1);
|
|
assert!(machine.cycles() > u64::from(VBLANK_START));
|
|
}
|
|
|
|
#[test]
|
|
fn unsupported_services_fail_at_an_explicit_instruction_trap() {
|
|
for service in [0x0d, 0x19, 0xff] {
|
|
let (mut machine, _) = call(service, false, [0, 0, 0]);
|
|
let error = (0..100)
|
|
.find_map(|_| machine.step().err())
|
|
.expect("unsupported SWI must fail");
|
|
assert!(matches!(
|
|
error,
|
|
MachineError::Cpu(CpuError::UnsupportedInstruction {
|
|
instruction: UNSUPPORTED_TRAP,
|
|
..
|
|
})
|
|
));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn cpu_set_fill_reads_one_value_and_writes_exact_count() {
|
|
for thumb in [false, true] {
|
|
for word in [false, true] {
|
|
let control = 3 | (1 << 24) | if word { 1 << 26 } else { 0 };
|
|
let (mut machine, return_pc) = call(0x0b, thumb, [SOURCE, DEST, control]);
|
|
machine.memory_mut().write32(SOURCE, 0xaabb_ccdd).unwrap();
|
|
for offset in 0..5 {
|
|
machine
|
|
.memory_mut()
|
|
.write32(DEST + offset * 4, 0x9999_9999)
|
|
.unwrap();
|
|
}
|
|
finish(&mut machine, return_pc);
|
|
let bytes = if word { 12 } else { 6 };
|
|
for offset in 0..bytes {
|
|
assert_eq!(
|
|
machine.memory().read8(DEST + offset).unwrap(),
|
|
[0xdd, 0xcc, 0xbb, 0xaa][(offset % if word { 4 } else { 2 }) as usize]
|
|
);
|
|
}
|
|
assert_eq!(machine.memory().read16(DEST + bytes).unwrap(), 0x9999);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn cpu_fast_set_rounds_up_to_eight_words_for_copy_and_fill() {
|
|
for thumb in [false, true] {
|
|
for fill in [false, true] {
|
|
for count in [1_u32, 7, 8, 9, 15, 16, 17] {
|
|
let (mut machine, return_pc) = call(
|
|
0x0c,
|
|
thumb,
|
|
[SOURCE, DEST, count | if fill { 1 << 24 } else { 0 }],
|
|
);
|
|
let rounded = (count + 7) & !7;
|
|
for index in 0..rounded + 1 {
|
|
machine
|
|
.memory_mut()
|
|
.write32(SOURCE + index * 4, 0x1000 + index)
|
|
.unwrap();
|
|
machine
|
|
.memory_mut()
|
|
.write32(DEST + index * 4, 0xcccc_cccc)
|
|
.unwrap();
|
|
}
|
|
let before = machine.cpu().clone();
|
|
finish(&mut machine, return_pc);
|
|
for index in 0..rounded {
|
|
assert_eq!(
|
|
machine.memory().read32(DEST + index * 4).unwrap(),
|
|
0x1000 + if fill { 0 } else { index }
|
|
);
|
|
}
|
|
assert_eq!(
|
|
machine.memory().read32(DEST + rounded * 4).unwrap(),
|
|
0xcccc_cccc
|
|
);
|
|
assert_eq!(&machine.cpu().registers()[..15], &before.registers()[..15]);
|
|
assert_eq!(machine.cpu().cpsr(), before.cpsr());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn fast_copy_reads_each_eight_word_block_before_writing_it() {
|
|
let (mut machine, return_pc) = call(0x0c, false, [SOURCE, SOURCE + 4, 16]);
|
|
let mut expected: Vec<u32> = (0..17).map(|index| 0x1234 + index).collect();
|
|
for (index, value) in expected.iter().enumerate() {
|
|
machine
|
|
.memory_mut()
|
|
.write32(SOURCE + index as u32 * 4, *value)
|
|
.unwrap();
|
|
}
|
|
for start in [0, 8] {
|
|
let block = expected[start..start + 8].to_vec();
|
|
expected[start + 1..start + 9].copy_from_slice(&block);
|
|
}
|
|
finish(&mut machine, return_pc);
|
|
for (index, value) in expected.into_iter().enumerate() {
|
|
assert_eq!(
|
|
machine.memory().read32(SOURCE + index as u32 * 4).unwrap(),
|
|
value
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn swi_callers_outside_user_and_system_modes_are_rejected() {
|
|
for mode in [0x11, 0x12, 0x13, 0x17, 0x1b] {
|
|
let (mut machine, _) = call_status(0x0b, false, [SOURCE, DEST, 0], mode);
|
|
let error = (0..100).find_map(|_| machine.step().err()).unwrap();
|
|
assert!(matches!(
|
|
error,
|
|
MachineError::Cpu(CpuError::UnsupportedInstruction {
|
|
instruction: UNSUPPORTED_TRAP,
|
|
..
|
|
})
|
|
));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn zero_length_copies_do_not_read_source_or_write_destination() {
|
|
for service in [0x0b, 0x0c] {
|
|
for control in [0, 1 << 24, 1 << 26, (1 << 24) | (1 << 26), 0xfae0_0000] {
|
|
let (mut machine, return_pc) =
|
|
call(service, false, [0x0e00_0000, 0x0e00_0000, control]);
|
|
finish(&mut machine, return_pc); // Both addresses would fail if accessed.
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn copy_count_ignores_reserved_bits_and_fast_set_always_uses_words() {
|
|
for service in [0x0b, 0x0c] {
|
|
let (mut machine, return_pc) = call(service, false, [SOURCE, DEST, 1 | 0xfae0_0000]);
|
|
machine.memory_mut().write32(SOURCE, 0x1234_abcd).unwrap();
|
|
finish(&mut machine, return_pc);
|
|
assert_eq!(
|
|
machine.memory().read32(DEST).unwrap(),
|
|
if service == 0x0b { 0xabcd } else { 0x1234_abcd }
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn copying_to_video_ram_uses_halfword_and_word_bus_writes() {
|
|
for service in [0x0b, 0x0c] {
|
|
let (mut machine, return_pc) = call(service, false, [SOURCE, VRAM_START, 3]);
|
|
machine.memory_mut().write32(SOURCE, 0x03e0_001f).unwrap();
|
|
machine
|
|
.memory_mut()
|
|
.write32(SOURCE + 4, 0x7fff_7c00)
|
|
.unwrap();
|
|
finish(&mut machine, return_pc);
|
|
assert_eq!(machine.memory().read32(VRAM_START).unwrap(), 0x03e0_001f);
|
|
assert_eq!(machine.memory().read16(VRAM_START + 4).unwrap(), 0x7c00);
|
|
assert_eq!(
|
|
machine.memory().read16(VRAM_START + 6).unwrap(),
|
|
if service == 0x0b { 0 } else { 0x7fff }
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn copy_addresses_align_down_and_overlaps_use_instruction_order() {
|
|
for word in [false, true] {
|
|
let (mut machine, return_pc) = call(
|
|
0x0b,
|
|
false,
|
|
[SOURCE + 3, DEST + 3, 1 | if word { 1 << 26 } else { 0 }],
|
|
);
|
|
machine.memory_mut().write32(SOURCE, 0x1234_abcd).unwrap();
|
|
finish(&mut machine, return_pc);
|
|
assert_eq!(
|
|
machine.memory().read32(DEST).unwrap(),
|
|
if word { 0x1234_abcd } else { 0x1234_0000 }
|
|
);
|
|
}
|
|
let (mut machine, return_pc) = call(0x0b, false, [SOURCE, SOURCE + 2, 4]);
|
|
machine.memory_mut().write32(SOURCE, 0xabcd_1234).unwrap();
|
|
finish(&mut machine, return_pc);
|
|
for offset in [0, 2, 4, 6, 8] {
|
|
assert_eq!(machine.memory().read16(SOURCE + offset).unwrap(), 0x1234);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn protected_or_wrapping_source_ranges_return_without_copying() {
|
|
for service in [0x0b, 0x0c] {
|
|
for source in [0, 0x3ffc, 0x0100_0000, 0x01ff_fffc, 0xffff_fffc] {
|
|
let (mut machine, return_pc) = call(service, false, [source, DEST, 2 | (1 << 26)]);
|
|
machine.memory_mut().write32(DEST, 0x1234_5678).unwrap();
|
|
finish(&mut machine, return_pc);
|
|
assert_eq!(machine.memory().read32(DEST).unwrap(), 0x1234_5678);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn copy_failure_keeps_earlier_writes_but_not_partial_instruction_changes() {
|
|
let (mut machine, _) = call(0x0b, false, [SOURCE, 0x07ff_fffc, 2 | (1 << 26)]);
|
|
machine.memory_mut().write32(SOURCE, 0xaabb_ccdd).unwrap();
|
|
let mut failed = false;
|
|
for _ in 0..100 {
|
|
let before = machine.cpu().clone();
|
|
let timing = machine.last_timing();
|
|
let cycles = machine.cycles();
|
|
if let Err(error) = machine.step() {
|
|
assert_eq!(
|
|
error,
|
|
MachineError::Cpu(MemoryError::ReadOnly(0x0800_0000).into())
|
|
);
|
|
assert_eq!(machine.cpu(), &before);
|
|
assert_eq!(machine.cycles(), cycles);
|
|
assert_eq!(machine.last_timing(), timing);
|
|
assert_eq!(machine.memory().read32(0x07ff_fffc).unwrap(), 0xaabb_ccdd);
|
|
failed = true;
|
|
break;
|
|
}
|
|
}
|
|
assert!(failed);
|
|
}
|
|
|
|
#[test]
|
|
fn halt_preserves_registers_and_ime_for_arm_and_thumb_callers() {
|
|
for thumb in [false, true] {
|
|
let (mut machine, return_pc) = call(2, thumb, [0x1234, 0x5678, 0x9abc]);
|
|
machine.memory_mut().write16(IE, 8).unwrap();
|
|
machine
|
|
.memory_mut()
|
|
.write32(TIMER_BASE, 0x00c0_fc18)
|
|
.unwrap(); // IRQ after 1,000 cycles.
|
|
let before = machine.cpu().clone();
|
|
machine.step().unwrap();
|
|
let mut slept = false;
|
|
for _ in 0..200 {
|
|
if machine.cpu().pc() == return_pc {
|
|
break;
|
|
}
|
|
slept |= machine.step().unwrap() == StepKind::HaltIdle;
|
|
}
|
|
assert!(slept);
|
|
assert_eq!(machine.cpu().pc(), return_pc);
|
|
assert_eq!(machine.memory().read16(IME).unwrap(), 0);
|
|
assert_eq!(machine.memory().read16(IF).unwrap(), 8);
|
|
assert_eq!(&machine.cpu().registers()[..15], &before.registers()[..15]);
|
|
assert_eq!(machine.cpu().cpsr(), before.cpsr());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn halt_with_pending_irq_returns_without_an_idle_interval() {
|
|
let (mut machine, return_pc) = call(2, false, [1, 2, 3]);
|
|
request_timer(machine.memory_mut());
|
|
machine.memory_mut().write16(IE, 8).unwrap();
|
|
machine.step().unwrap();
|
|
for _ in 0..100 {
|
|
if machine.cpu().pc() == return_pc {
|
|
break;
|
|
}
|
|
assert_ne!(machine.step().unwrap(), StepKind::HaltIdle);
|
|
}
|
|
assert_eq!(machine.cpu().pc(), return_pc);
|
|
}
|
|
|
|
fn request_timer(bus: &mut Memory) {
|
|
bus.write32(TIMER_BASE, 0x00c0_ffff).unwrap();
|
|
bus.advance_cycles(1);
|
|
bus.write16(TIMER_BASE + 2, 0).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn interrupt_wait_has_no_lost_wake_between_flag_check_and_halt() {
|
|
for injection_step in 0..100 {
|
|
let (mut machine, return_pc) = call(4, false, [0, 8, 0]);
|
|
callback(machine.memory_mut());
|
|
machine.memory_mut().write16(IE, 8).unwrap();
|
|
for step in 0..500 {
|
|
if step == injection_step {
|
|
request_timer(machine.memory_mut());
|
|
}
|
|
if machine.cpu().pc() == return_pc {
|
|
break;
|
|
}
|
|
machine.step().unwrap();
|
|
}
|
|
assert_eq!(
|
|
machine.cpu().pc(),
|
|
return_pc,
|
|
"injection step {injection_step}"
|
|
);
|
|
assert_eq!(machine.memory().read16(IF).unwrap(), 0);
|
|
assert_eq!(machine.memory().read16(IRQ_FLAGS).unwrap(), 0);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn discard_old_flags_waits_for_a_new_callback_and_retains_other_flags() {
|
|
for thumb in [false, true] {
|
|
let (mut machine, return_pc) = call(4, thumb, [1, 8, 0]);
|
|
callback(machine.memory_mut());
|
|
machine.memory_mut().write16(IE, 8).unwrap();
|
|
machine.memory_mut().write16(IRQ_FLAGS, 9).unwrap();
|
|
for _ in 0..100 {
|
|
if machine.halted() {
|
|
break;
|
|
}
|
|
machine.step().unwrap();
|
|
}
|
|
assert!(machine.halted());
|
|
assert_eq!(machine.memory().read16(IRQ_FLAGS).unwrap(), 1);
|
|
request_timer(machine.memory_mut());
|
|
reach(&mut machine, return_pc, 200);
|
|
assert_eq!(machine.memory().read16(IRQ_FLAGS).unwrap(), 1);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn unrelated_irq_does_not_complete_a_wait_for_vblank() {
|
|
let (mut machine, return_pc) = call(4, false, [1, 1, 0]);
|
|
callback(machine.memory_mut());
|
|
machine.memory_mut().write16(IE, 9).unwrap();
|
|
machine.memory_mut().write16(DISPSTAT, 8).unwrap();
|
|
machine
|
|
.memory_mut()
|
|
.write32(TIMER_BASE, 0x00c0_fc18)
|
|
.unwrap();
|
|
let mut irqs = 0;
|
|
for _ in 0..1000 {
|
|
if machine.cpu().pc() == return_pc {
|
|
break;
|
|
}
|
|
if machine.step().unwrap() == StepKind::IrqEntry {
|
|
irqs += 1;
|
|
machine.memory_mut().write16(TIMER_BASE + 2, 0).unwrap();
|
|
}
|
|
}
|
|
assert_eq!(machine.cpu().pc(), return_pc);
|
|
assert_eq!(irqs, 2);
|
|
assert_eq!(machine.memory().read16(IRQ_FLAGS).unwrap(), 8);
|
|
assert!(machine.cycles() > u64::from(VBLANK_START));
|
|
}
|
|
|
|
#[test]
|
|
fn wait_services_restore_user_system_and_irq_masked_caller_status() {
|
|
for thumb in [false, true] {
|
|
for status in [0x10, 0x90, 0x9f, 0xdf] {
|
|
let (mut machine, return_pc) = call_status(4, thumb, [0, 8, 0], status);
|
|
callback(machine.memory_mut());
|
|
machine.memory_mut().write16(IE, 8).unwrap();
|
|
let before = machine.cpu().clone();
|
|
for _ in 0..100 {
|
|
if machine.halted() {
|
|
break;
|
|
}
|
|
machine.step().unwrap();
|
|
}
|
|
assert!(machine.halted());
|
|
request_timer(machine.memory_mut());
|
|
reach(&mut machine, return_pc, 200);
|
|
assert_eq!(machine.cpu().cpsr(), before.cpsr());
|
|
assert_eq!(&machine.cpu().registers()[..15], &before.registers()[..15]);
|
|
assert_eq!(machine.memory().read16(IME).unwrap(), 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn missing_or_misaligned_irq_callback_is_an_explicit_trap() {
|
|
for pointer in [0, CALLBACK + 1, CALLBACK + 2] {
|
|
let (mut machine, _) = call(5, false, [0, 0, 0]);
|
|
machine.memory_mut().write32(IRQ_HANDLER, pointer).unwrap();
|
|
machine.memory_mut().write16(IE, 8).unwrap();
|
|
machine.memory_mut().write16(IME, 1).unwrap();
|
|
request_timer(machine.memory_mut());
|
|
let error = (0..100).find_map(|_| machine.step().err()).unwrap();
|
|
assert!(matches!(
|
|
error,
|
|
MachineError::Cpu(CpuError::UnsupportedInstruction {
|
|
instruction: UNSUPPORTED_TRAP,
|
|
..
|
|
})
|
|
));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn repeated_calls_restore_the_supervisor_stack() {
|
|
let mut machine = bios::boot(words(&[0xe3a0_2000, 0xef0b_0000, 0xeaff_fffd])).unwrap();
|
|
reach(&mut machine, ROM_START + 4, 100);
|
|
for _ in 0..100 {
|
|
machine.step().unwrap(); // SWI
|
|
assert_eq!(machine.cpu().registers()[13], bios::SVC_STACK);
|
|
reach(&mut machine, ROM_START + 8, 100);
|
|
assert_eq!(machine.cpu().registers()[13], SYSTEM_STACK);
|
|
machine.step().unwrap(); // Branch back to SWI.
|
|
assert_eq!(machine.cpu().pc(), ROM_START + 4);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn arm_swi_uses_upper_comment_byte_not_low_byte() {
|
|
let mut machine = bios::boot(words(&[0xe3a0_2000, 0xef0b_ff03, 0xeaff_fffe])).unwrap();
|
|
reach(&mut machine, ROM_START + 4, 100);
|
|
finish(&mut machine, ROM_START + 8); // CpuSet(count=0), not Stop(3).
|
|
}
|
|
|
|
#[test]
|
|
fn callback_must_record_bios_flags_not_only_acknowledge_hardware_if() {
|
|
let (mut machine, return_pc) = call(4, false, [0, 8, 0]);
|
|
callback(machine.memory_mut());
|
|
machine
|
|
.memory_mut()
|
|
.write32(CALLBACK + 12, 0xe12f_ff1e)
|
|
.unwrap(); // Return after IF acknowledgement, omit flags.
|
|
machine.memory_mut().write16(IE, 8).unwrap();
|
|
request_timer(machine.memory_mut());
|
|
for _ in 0..300 {
|
|
machine.step().unwrap();
|
|
}
|
|
assert_ne!(machine.cpu().pc(), return_pc);
|
|
assert!(machine.halted());
|
|
assert_eq!(machine.memory().read16(IF).unwrap(), 0);
|
|
assert_eq!(machine.memory().read16(IRQ_FLAGS).unwrap(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn supplying_an_image_is_optional_and_does_not_change_memory_new() {
|
|
assert_eq!(
|
|
Memory::new(vec![]).unwrap().read32(0),
|
|
Err(MemoryError::Unmapped(0))
|
|
);
|
|
let mut bus = Memory::with_bios(vec![], bios::image()).unwrap();
|
|
assert_eq!(bus.write32(0, 1), Err(MemoryError::ReadOnly(0)));
|
|
assert_eq!(
|
|
bus.read8(BIOS_SIZE as u32),
|
|
Err(MemoryError::Unmapped(BIOS_SIZE as u32))
|
|
);
|
|
}
|