Fix HL+, add cpu instr testsuite
This commit is contained in:
parent
e3fce0bcc5
commit
d91fa7d75b
@ -85,9 +85,9 @@ impl Cartridge {
|
|||||||
match addr {
|
match addr {
|
||||||
0x0000 ... 0x3FFF => self.rom[addr as usize],
|
0x0000 ... 0x3FFF => self.rom[addr as usize],
|
||||||
0x4000 ... 0x7FFF => {
|
0x4000 ... 0x7FFF => {
|
||||||
|
let addr = addr - 0x4000;
|
||||||
let abs_addr: usize = addr as usize + self.bank_no as usize * 0x4000;
|
let abs_addr: usize = addr as usize + self.bank_no as usize * 0x4000;
|
||||||
let val: u8 = self.rom[abs_addr];
|
let val: u8 = self.rom[abs_addr];
|
||||||
// println!("Access {:04X}{:04X} = {:02X}", self.bank_no, addr, val);
|
|
||||||
val
|
val
|
||||||
},
|
},
|
||||||
0xA000 ... 0xBFFF => {
|
0xA000 ... 0xBFFF => {
|
||||||
@ -143,7 +143,7 @@ impl Cartridge {
|
|||||||
|
|
||||||
pub fn write_byte(&mut self, addr: u16, val: u8) {
|
pub fn write_byte(&mut self, addr: u16, val: u8) {
|
||||||
match self.mbc_type {
|
match self.mbc_type {
|
||||||
MemoryBankControllerType::None => panic!("Cartridge: No MBC found, can not write to ROM"),
|
MemoryBankControllerType::None => println!("Cartridge: No MBC found, can not write to ROM ({:02X} to {:04X})", val, addr),
|
||||||
MemoryBankControllerType::MBC3 => self.write_byte_mbc3(addr, val),
|
MemoryBankControllerType::MBC3 => self.write_byte_mbc3(addr, val),
|
||||||
_ => panic!("MBC not supported.")
|
_ => panic!("MBC not supported.")
|
||||||
}
|
}
|
||||||
|
|||||||
228
src/cpu.rs
228
src/cpu.rs
@ -36,6 +36,8 @@ pub struct CPU {
|
|||||||
|
|
||||||
ime: bool,
|
ime: bool,
|
||||||
debug: bool,
|
debug: bool,
|
||||||
|
|
||||||
|
halted: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_u16(bytes: Box<[u8]>) -> u16 {
|
fn to_u16(bytes: Box<[u8]>) -> u16 {
|
||||||
@ -52,6 +54,7 @@ impl CPU {
|
|||||||
interconnect: interconnect,
|
interconnect: interconnect,
|
||||||
ime: false, // Is this correct?
|
ime: false, // Is this correct?
|
||||||
debug: false,
|
debug: false,
|
||||||
|
halted: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,6 +128,30 @@ impl CPU {
|
|||||||
self.clear_flag(FLAG_N);
|
self.clear_flag(FLAG_N);
|
||||||
self.clear_flag(FLAG_H);
|
self.clear_flag(FLAG_H);
|
||||||
},
|
},
|
||||||
|
0x18 ... 0x1F => { // RR
|
||||||
|
let reg_id = (instruction - 0x18) as usize;
|
||||||
|
let val = self.get_8bit_reg(reg_id);
|
||||||
|
if self.debug {
|
||||||
|
println!("RR {}", REG_NAMES[reg_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let carry = self.flags & FLAG_C > 0;
|
||||||
|
if !carry {
|
||||||
|
// No carry before, now we got a carry => set it
|
||||||
|
if val & 0x80 == 0x80 {
|
||||||
|
self.set_flag(FLAG_C);
|
||||||
|
}
|
||||||
|
self.set_8bit_reg(reg_id, val >> 1);
|
||||||
|
} else {
|
||||||
|
if val & 0x80 == 0 {
|
||||||
|
self.clear_flag(FLAG_C);
|
||||||
|
}
|
||||||
|
self.set_8bit_reg(reg_id, val >> 1 | 1 << 7);
|
||||||
|
}
|
||||||
|
self.clear_flag(FLAG_Z);
|
||||||
|
self.clear_flag(FLAG_N);
|
||||||
|
self.clear_flag(FLAG_H);
|
||||||
|
},
|
||||||
0x30 ... 0x37 => {
|
0x30 ... 0x37 => {
|
||||||
let reg_id = (instruction - 0x30) as usize;
|
let reg_id = (instruction - 0x30) as usize;
|
||||||
if self.debug {
|
if self.debug {
|
||||||
@ -132,7 +159,15 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
let v: u8 = self.get_8bit_reg(reg_id);
|
let v: u8 = self.get_8bit_reg(reg_id);
|
||||||
self.set_8bit_reg(reg_id, v << 4 | v >> 4);
|
self.set_8bit_reg(reg_id, v << 4 | v >> 4);
|
||||||
|
},
|
||||||
|
0x38 ... 0x3F => {
|
||||||
|
let reg_id = (instruction - 0x38) as usize;
|
||||||
|
if self.debug {
|
||||||
|
println!("SRL {}", REG_NAMES[reg_id]);
|
||||||
}
|
}
|
||||||
|
let v: u8 = self.get_8bit_reg(reg_id);
|
||||||
|
self.set_8bit_reg(reg_id, v << 1);
|
||||||
|
},
|
||||||
0x40 ... 0x47 => {
|
0x40 ... 0x47 => {
|
||||||
// Test 0th bit
|
// Test 0th bit
|
||||||
let reg_id = (instruction - 0x40) as usize;
|
let reg_id = (instruction - 0x40) as usize;
|
||||||
@ -362,10 +397,9 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
let val = self.get_8bit_reg(reg_id).wrapping_add(1);
|
let val = self.get_8bit_reg(reg_id).wrapping_add(1);
|
||||||
self.set_8bit_reg(reg_id, val);
|
self.set_8bit_reg(reg_id, val);
|
||||||
if val == 0 {
|
self.set_clear_flag(FLAG_Z, val == 0);
|
||||||
self.flags |= FLAG_Z;
|
self.set_clear_flag(FLAG_C, val == 0);
|
||||||
}
|
self.clear_flag(FLAG_N);
|
||||||
self.flags &= !FLAG_N;
|
|
||||||
4
|
4
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,12 +416,10 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
let val = self.get_8bit_reg(reg_id).wrapping_sub(1);
|
let val = self.get_8bit_reg(reg_id).wrapping_sub(1);
|
||||||
self.set_8bit_reg(reg_id, val);
|
self.set_8bit_reg(reg_id, val);
|
||||||
if val == 0 {
|
|
||||||
self.flags |= FLAG_Z;
|
self.set_clear_flag(FLAG_Z, val == 0);
|
||||||
} else {
|
self.set_clear_flag(FLAG_C, val == 255);
|
||||||
self.flags &= !FLAG_Z;
|
self.set_flag(FLAG_N);
|
||||||
}
|
|
||||||
self.flags |= FLAG_N;
|
|
||||||
4
|
4
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,6 +441,14 @@ impl CPU {
|
|||||||
self.flags &= !flag;
|
self.flags &= !flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_clear_flag(&mut self, flag: u8, dep: bool) {
|
||||||
|
if dep {
|
||||||
|
self.set_flag(flag);
|
||||||
|
} else {
|
||||||
|
self.clear_flag(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn int_(&mut self, val: u8){
|
fn int_(&mut self, val: u8){
|
||||||
if self.debug {
|
if self.debug {
|
||||||
println!("INT {:02X}", val);
|
println!("INT {:02X}", val);
|
||||||
@ -429,6 +469,16 @@ impl CPU {
|
|||||||
16
|
16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_interrupt(&mut self, offset: u8, flag: u8) {
|
||||||
|
// Remove interrupt requested flag
|
||||||
|
let new_flag = self.interconnect.read_byte(0xFF0F) & !flag;
|
||||||
|
self.interconnect.write_byte(0xFF0F, new_flag);
|
||||||
|
|
||||||
|
// Run interrupt handler
|
||||||
|
self.int_(offset);
|
||||||
|
self.halted = false;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run_instruction(&mut self) {
|
pub fn run_instruction(&mut self) {
|
||||||
// self.debug = !self.interconnect.is_boot_rom();
|
// self.debug = !self.interconnect.is_boot_rom();
|
||||||
// Check for interrupts.
|
// Check for interrupts.
|
||||||
@ -441,18 +491,17 @@ impl CPU {
|
|||||||
|
|
||||||
if e_pending & interconnect::INTERRUPT_DISPLAY_VBLANK > 0 {
|
if e_pending & interconnect::INTERRUPT_DISPLAY_VBLANK > 0 {
|
||||||
println!("Handling vblank interrupt");
|
println!("Handling vblank interrupt");
|
||||||
self.interconnect.write_byte(0xFF0F, pending & !interconnect::INTERRUPT_DISPLAY_VBLANK);
|
self.handle_interrupt(0x40, interconnect::INTERRUPT_DISPLAY_VBLANK);
|
||||||
self.int_(0x40);
|
|
||||||
return;
|
|
||||||
} else if e_pending & interconnect::INTERRUPT_DISPLAY_STAT > 0 {
|
} else if e_pending & interconnect::INTERRUPT_DISPLAY_STAT > 0 {
|
||||||
println!("Handling display stat interrupt");
|
println!("Handling display stat interrupt");
|
||||||
self.interconnect.write_byte(0xFF0F, pending & !interconnect::INTERRUPT_DISPLAY_STAT);
|
self.handle_interrupt(0x40, interconnect::INTERRUPT_DISPLAY_STAT);
|
||||||
self.int_(0x48);
|
|
||||||
return;
|
|
||||||
} else if e_pending > 0 {
|
} else if e_pending > 0 {
|
||||||
panic!("Unknown pending interrupt: {:02X}", e_pending);
|
panic!("Unknown pending interrupt: {:02X}", e_pending);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut cycles: u8 = 1;
|
||||||
|
if !self.halted {
|
||||||
// We need to double-check the flags
|
// We need to double-check the flags
|
||||||
let instruction = self.read_byte(self.ip);
|
let instruction = self.read_byte(self.ip);
|
||||||
if self.debug {
|
if self.debug {
|
||||||
@ -472,7 +521,7 @@ impl CPU {
|
|||||||
|
|
||||||
self.ip = self.ip.wrapping_add(1);
|
self.ip = self.ip.wrapping_add(1);
|
||||||
|
|
||||||
let cycles: u8 = match instruction {
|
cycles = match instruction {
|
||||||
0x00 => {
|
0x00 => {
|
||||||
if self.debug {
|
if self.debug {
|
||||||
println!("NOP");
|
println!("NOP");
|
||||||
@ -481,6 +530,7 @@ impl CPU {
|
|||||||
},
|
},
|
||||||
0x01 => self.ld_rr_vv(REG_N_B, REG_N_C),
|
0x01 => self.ld_rr_vv(REG_N_B, REG_N_C),
|
||||||
0x02 => self.ld_dref_rr_a(REG_N_B, REG_N_C),
|
0x02 => self.ld_dref_rr_a(REG_N_B, REG_N_C),
|
||||||
|
0x03 => self.inc_rr(REG_N_B, REG_N_C),
|
||||||
0x04 => self.reg_inc(REG_N_B),
|
0x04 => self.reg_inc(REG_N_B),
|
||||||
0x05 => self.reg_dec(REG_N_B),
|
0x05 => self.reg_dec(REG_N_B),
|
||||||
0x06 => self.ld_r_v(REG_N_B),
|
0x06 => self.ld_r_v(REG_N_B),
|
||||||
@ -551,6 +601,28 @@ impl CPU {
|
|||||||
0x1C => self.reg_inc(REG_N_E),
|
0x1C => self.reg_inc(REG_N_E),
|
||||||
0x1D => self.reg_dec(REG_N_E),
|
0x1D => self.reg_dec(REG_N_E),
|
||||||
0x1E => self.ld_r_v(REG_N_E),
|
0x1E => self.ld_r_v(REG_N_E),
|
||||||
|
0x1F => { // UNTESTED
|
||||||
|
if self.debug {
|
||||||
|
println!("RRA");
|
||||||
|
}
|
||||||
|
let carry = self.flags & FLAG_C > 0;
|
||||||
|
if !carry {
|
||||||
|
// No carry before, now we got a carry => set it
|
||||||
|
if self.regs[REG_A] & 1 == 1 {
|
||||||
|
self.set_flag(FLAG_C);
|
||||||
|
}
|
||||||
|
self.regs[REG_A] = self.regs[REG_A] >> 1;
|
||||||
|
} else {
|
||||||
|
if self.regs[REG_A] & 1 == 0 {
|
||||||
|
self.clear_flag(FLAG_C);
|
||||||
|
}
|
||||||
|
self.regs[REG_A] = self.regs[REG_A] >> 1 | 0x80;
|
||||||
|
}
|
||||||
|
self.clear_flag(FLAG_Z);
|
||||||
|
self.clear_flag(FLAG_N);
|
||||||
|
self.clear_flag(FLAG_H);
|
||||||
|
4
|
||||||
|
},
|
||||||
0x20 => {
|
0x20 => {
|
||||||
let args = self.load_args(1);
|
let args = self.load_args(1);
|
||||||
if self.debug {
|
if self.debug {
|
||||||
@ -575,7 +647,7 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
let addr: u16 = self.get_pair_value(REG_H, REG_L);
|
let addr: u16 = self.get_pair_value(REG_H, REG_L);
|
||||||
self.interconnect.write_byte(addr, self.regs[REG_A]);
|
self.interconnect.write_byte(addr, self.regs[REG_A]);
|
||||||
self.set_pair_value(REG_H, REG_L, addr + 1);
|
self.set_pair_value(REG_H, REG_L, addr.wrapping_add(1));
|
||||||
8
|
8
|
||||||
},
|
},
|
||||||
0x23 => self.inc_rr(REG_H, REG_L),
|
0x23 => self.inc_rr(REG_H, REG_L),
|
||||||
@ -607,6 +679,7 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
let addr: u16 = self.get_pair_value(REG_H, REG_L);
|
let addr: u16 = self.get_pair_value(REG_H, REG_L);
|
||||||
self.regs[REG_A] = self.interconnect.read_byte(addr);
|
self.regs[REG_A] = self.interconnect.read_byte(addr);
|
||||||
|
self.set_pair_value(REG_H, REG_L, addr+1);
|
||||||
8
|
8
|
||||||
},
|
},
|
||||||
0x2B => self.dec_rr(REG_N_H, REG_N_L),
|
0x2B => self.dec_rr(REG_N_H, REG_N_L),
|
||||||
@ -656,6 +729,7 @@ impl CPU {
|
|||||||
self.set_pair_value(REG_H, REG_L, addr);
|
self.set_pair_value(REG_H, REG_L, addr);
|
||||||
8
|
8
|
||||||
},
|
},
|
||||||
|
0x35 => self.reg_dec(REG_N_HL),
|
||||||
0x36 => {
|
0x36 => {
|
||||||
let args = self.load_args(1);
|
let args = self.load_args(1);
|
||||||
if self.debug {
|
if self.debug {
|
||||||
@ -719,6 +793,15 @@ impl CPU {
|
|||||||
0x70 ... 0x75 | 0x77 => self.ld_r_r(REG_N_HL, (instruction - 0x70) as usize),
|
0x70 ... 0x75 | 0x77 => self.ld_r_r(REG_N_HL, (instruction - 0x70) as usize),
|
||||||
0x78 ... 0x7F => self.ld_r_r(REG_N_A, (instruction - 0x78) as usize),
|
0x78 ... 0x7F => self.ld_r_r(REG_N_A, (instruction - 0x78) as usize),
|
||||||
|
|
||||||
|
// HALT
|
||||||
|
0x76 => {
|
||||||
|
if self.debug {
|
||||||
|
println!("HALT");
|
||||||
|
}
|
||||||
|
self.halted = true;
|
||||||
|
4
|
||||||
|
},
|
||||||
|
|
||||||
// ADD
|
// ADD
|
||||||
0x80 ... 0x87 => {
|
0x80 ... 0x87 => {
|
||||||
let reg_id = (instruction - 0x80) as usize;
|
let reg_id = (instruction - 0x80) as usize;
|
||||||
@ -838,18 +921,11 @@ impl CPU {
|
|||||||
println!("CP {}", REG_NAMES[reg_id]);
|
println!("CP {}", REG_NAMES[reg_id]);
|
||||||
}
|
}
|
||||||
let val = self.get_8bit_reg(reg_id);
|
let val = self.get_8bit_reg(reg_id);
|
||||||
|
let rval = self.regs[REG_A];
|
||||||
|
|
||||||
self.set_flag(FLAG_N);
|
self.set_flag(FLAG_N);
|
||||||
if self.regs[REG_A] < val {
|
self.set_clear_flag(FLAG_C, rval < val);
|
||||||
self.set_flag(FLAG_C);
|
self.set_clear_flag(FLAG_Z, rval == val);
|
||||||
self.clear_flag(FLAG_Z);
|
|
||||||
} else if self.regs[REG_A] == val {
|
|
||||||
self.clear_flag(FLAG_C);
|
|
||||||
self.set_flag(FLAG_Z);
|
|
||||||
} else {
|
|
||||||
self.clear_flag(FLAG_C);
|
|
||||||
self.clear_flag(FLAG_Z);
|
|
||||||
}
|
|
||||||
4
|
4
|
||||||
},
|
},
|
||||||
0xC0 => {
|
0xC0 => {
|
||||||
@ -950,7 +1026,36 @@ impl CPU {
|
|||||||
self.call(target);
|
self.call(target);
|
||||||
24
|
24
|
||||||
},
|
},
|
||||||
|
0xCE => {
|
||||||
|
let arg = self.load_args(1)[0];
|
||||||
|
if self.debug {
|
||||||
|
println!("ADC {:02X}", arg);
|
||||||
|
}
|
||||||
|
self.regs[REG_A] = self.regs[REG_A].wrapping_add(arg);
|
||||||
|
if self.flags & FLAG_C > 0 {
|
||||||
|
self.regs[REG_A] = self.regs[REG_A].wrapping_add(1);
|
||||||
|
}
|
||||||
|
if self.regs[REG_A] == 0 {
|
||||||
|
self.set_flag(FLAG_Z);
|
||||||
|
} else {
|
||||||
|
self.clear_flag(FLAG_Z);
|
||||||
|
}
|
||||||
|
self.clear_flag(FLAG_N);
|
||||||
|
// TODO: FLAG_H, FLAG_C
|
||||||
|
8
|
||||||
|
},
|
||||||
0xCF => self.rst(0x08),
|
0xCF => self.rst(0x08),
|
||||||
|
0xD0 => {
|
||||||
|
if self.debug {
|
||||||
|
println!("RET NC");
|
||||||
|
}
|
||||||
|
if self.flags & FLAG_C == 0 {
|
||||||
|
self.ret();
|
||||||
|
20
|
||||||
|
} else {
|
||||||
|
8
|
||||||
|
}
|
||||||
|
},
|
||||||
0xD1 => self.pop_rr(REG_D, REG_E),
|
0xD1 => self.pop_rr(REG_D, REG_E),
|
||||||
0xD5 => self.push_rr(REG_D, REG_E),
|
0xD5 => self.push_rr(REG_D, REG_E),
|
||||||
0xD6 => {
|
0xD6 => {
|
||||||
@ -958,10 +1063,45 @@ impl CPU {
|
|||||||
if self.debug {
|
if self.debug {
|
||||||
println!("SUB {:02X}", val);
|
println!("SUB {:02X}", val);
|
||||||
}
|
}
|
||||||
self.regs[REG_A] = self.regs[REG_A].wrapping_sub(val);
|
|
||||||
|
let rval = self.regs[REG_A];
|
||||||
|
self.set_clear_flag(FLAG_C, val > rval);
|
||||||
|
self.set_clear_flag(FLAG_Z, val == rval);
|
||||||
|
self.set_flag(FLAG_N);
|
||||||
|
|
||||||
|
self.regs[REG_A] = rval.wrapping_sub(val);
|
||||||
8
|
8
|
||||||
},
|
},
|
||||||
0xD7 => self.rst(0x10),
|
0xD7 => self.rst(0x10),
|
||||||
|
0xD8 => {
|
||||||
|
if self.debug {
|
||||||
|
println!("RET C");
|
||||||
|
}
|
||||||
|
if self.flags & FLAG_C > 0 {
|
||||||
|
self.ret();
|
||||||
|
20
|
||||||
|
} else {
|
||||||
|
8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0xDE => {
|
||||||
|
let arg = self.load_args(1)[0];
|
||||||
|
if self.debug {
|
||||||
|
println!("SBC {:02X}", arg);
|
||||||
|
}
|
||||||
|
self.regs[REG_A] = self.regs[REG_A].wrapping_sub(arg);
|
||||||
|
if self.flags & FLAG_C > 0 {
|
||||||
|
self.regs[REG_A] = self.regs[REG_A].wrapping_sub(1);
|
||||||
|
}
|
||||||
|
if self.regs[REG_A] == 0 {
|
||||||
|
self.set_flag(FLAG_Z);
|
||||||
|
} else {
|
||||||
|
self.clear_flag(FLAG_Z);
|
||||||
|
}
|
||||||
|
self.set_flag(FLAG_N);
|
||||||
|
// TODO: FLAG_H, FLAG_C
|
||||||
|
8
|
||||||
|
},
|
||||||
0xDF => self.rst(0x18),
|
0xDF => self.rst(0x18),
|
||||||
0xE0 => {
|
0xE0 => {
|
||||||
let args = self.load_args(1);
|
let args = self.load_args(1);
|
||||||
@ -1006,6 +1146,22 @@ impl CPU {
|
|||||||
self.interconnect.write_byte(addr, self.regs[REG_A]);
|
self.interconnect.write_byte(addr, self.regs[REG_A]);
|
||||||
16
|
16
|
||||||
},
|
},
|
||||||
|
0xEE => {
|
||||||
|
let arg = self.load_args(1)[0];
|
||||||
|
if self.debug {
|
||||||
|
println!("XOR {:02X}", arg);
|
||||||
|
}
|
||||||
|
self.regs[REG_A] ^= arg;
|
||||||
|
if self.regs[REG_A] == 0 {
|
||||||
|
self.set_flag(FLAG_Z);
|
||||||
|
} else {
|
||||||
|
self.clear_flag(FLAG_Z);
|
||||||
|
}
|
||||||
|
self.clear_flag(FLAG_H);
|
||||||
|
self.clear_flag(FLAG_C);
|
||||||
|
self.clear_flag(FLAG_N);
|
||||||
|
8
|
||||||
|
},
|
||||||
0xEF => self.rst(0x28),
|
0xEF => self.rst(0x28),
|
||||||
0xF0 => {
|
0xF0 => {
|
||||||
let args = self.load_args(1);
|
let args = self.load_args(1);
|
||||||
@ -1059,20 +1215,13 @@ impl CPU {
|
|||||||
},
|
},
|
||||||
0xFE => {
|
0xFE => {
|
||||||
let args = self.load_args(1);
|
let args = self.load_args(1);
|
||||||
|
let rval = self.regs[REG_A];
|
||||||
if self.debug {
|
if self.debug {
|
||||||
println!("CP {:02X}", args[0]);
|
println!("CP {:02X}", args[0]);
|
||||||
}
|
}
|
||||||
self.set_flag(FLAG_N);
|
self.set_flag(FLAG_N);
|
||||||
if args[0] > self.regs[REG_A] {
|
self.set_clear_flag(FLAG_C, args[0] > rval);
|
||||||
self.set_flag(FLAG_C);
|
self.set_clear_flag(FLAG_Z, args[0] == rval);
|
||||||
} else {
|
|
||||||
self.clear_flag(FLAG_C);
|
|
||||||
}
|
|
||||||
if args[0] == self.regs[REG_A] {
|
|
||||||
self.set_flag(FLAG_Z);
|
|
||||||
} else {
|
|
||||||
self.clear_flag(FLAG_Z);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO H
|
// TODO H
|
||||||
8
|
8
|
||||||
@ -1080,6 +1229,7 @@ impl CPU {
|
|||||||
0xFF => self.rst(0x38),
|
0xFF => self.rst(0x38),
|
||||||
_ => panic!("Unknown instruction: {:02x}", instruction)
|
_ => panic!("Unknown instruction: {:02x}", instruction)
|
||||||
};
|
};
|
||||||
|
}
|
||||||
// self.dump_stack();
|
// self.dump_stack();
|
||||||
self.interconnect.tick(cycles);
|
self.interconnect.tick(cycles);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -135,7 +135,7 @@ impl Display {
|
|||||||
self.vram[(addr - 0x8000) as usize] = val;
|
self.vram[(addr - 0x8000) as usize] = val;
|
||||||
}
|
}
|
||||||
0xFE00 ... 0xFE9F => {
|
0xFE00 ... 0xFE9F => {
|
||||||
println!("OAM: Write {:02X} to {:04X}", val, addr);
|
// println!("OAM: Write {:02X} to {:04X}", val, addr);
|
||||||
self.oam[(addr - 0xFE00) as usize] = val;
|
self.oam[(addr - 0xFE00) as usize] = val;
|
||||||
}
|
}
|
||||||
0xFF40 => self.control = val,
|
0xFF40 => self.control = val,
|
||||||
|
|||||||
@ -63,18 +63,6 @@ impl Interconnect {
|
|||||||
self.disable_bootrom == 0
|
self.disable_bootrom == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn vblank_interrupt(&mut self) -> bool {
|
|
||||||
// This should set 0xFF0F accordingly and the CPU should
|
|
||||||
// handle the interrupt generation. So this is WRONG!
|
|
||||||
// the interrupt WAITS
|
|
||||||
if self.display.vblank_interrupt() && self.interrupt & INTERRUPT_DISPLAY_VBLANK > 0 {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_byte(&self, addr: u16) -> u8 {
|
pub fn read_byte(&self, addr: u16) -> u8 {
|
||||||
// TODO: Make this more beautiful.
|
// TODO: Make this more beautiful.
|
||||||
// TODO: if some flag set, use bios, otherwise only use rom
|
// TODO: if some flag set, use bios, otherwise only use rom
|
||||||
|
|||||||
BIN
tests/cpu_instrs/cpu_instrs.gb
Normal file
BIN
tests/cpu_instrs/cpu_instrs.gb
Normal file
Binary file not shown.
BIN
tests/cpu_instrs/individual/01-special.gb
Normal file
BIN
tests/cpu_instrs/individual/01-special.gb
Normal file
Binary file not shown.
BIN
tests/cpu_instrs/individual/02-interrupts.gb
Normal file
BIN
tests/cpu_instrs/individual/02-interrupts.gb
Normal file
Binary file not shown.
BIN
tests/cpu_instrs/individual/03-op sp,hl.gb
Normal file
BIN
tests/cpu_instrs/individual/03-op sp,hl.gb
Normal file
Binary file not shown.
BIN
tests/cpu_instrs/individual/04-op r,imm.gb
Normal file
BIN
tests/cpu_instrs/individual/04-op r,imm.gb
Normal file
Binary file not shown.
BIN
tests/cpu_instrs/individual/05-op rp.gb
Normal file
BIN
tests/cpu_instrs/individual/05-op rp.gb
Normal file
Binary file not shown.
BIN
tests/cpu_instrs/individual/06-ld r,r.gb
Normal file
BIN
tests/cpu_instrs/individual/06-ld r,r.gb
Normal file
Binary file not shown.
BIN
tests/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb
Normal file
BIN
tests/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb
Normal file
Binary file not shown.
BIN
tests/cpu_instrs/individual/08-misc instrs.gb
Normal file
BIN
tests/cpu_instrs/individual/08-misc instrs.gb
Normal file
Binary file not shown.
BIN
tests/cpu_instrs/individual/09-op r,r.gb
Normal file
BIN
tests/cpu_instrs/individual/09-op r,r.gb
Normal file
Binary file not shown.
BIN
tests/cpu_instrs/individual/10-bit ops.gb
Normal file
BIN
tests/cpu_instrs/individual/10-bit ops.gb
Normal file
Binary file not shown.
BIN
tests/cpu_instrs/individual/11-op a,(hl).gb
Normal file
BIN
tests/cpu_instrs/individual/11-op a,(hl).gb
Normal file
Binary file not shown.
119
tests/cpu_instrs/readme.txt
Normal file
119
tests/cpu_instrs/readme.txt
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
Game Boy CPU Instruction Behavior Test
|
||||||
|
--------------------------------------
|
||||||
|
This ROM tests the behavior of all CPU instructions except STOP and the
|
||||||
|
11 illegal opcodes. The tests are fairly thorough, running instructions
|
||||||
|
with boundary data and verifying both the result and that other
|
||||||
|
registers are not modified. Instructions which perform the same
|
||||||
|
operation on different registers are each tested just as thoroughly, in
|
||||||
|
case an emulator implements each independently. Some sub-tests take half
|
||||||
|
minute to complete.
|
||||||
|
|
||||||
|
Failed instructions are listed as
|
||||||
|
|
||||||
|
[CB] opcode
|
||||||
|
|
||||||
|
Some errors cannot of course be diagnosed properly, since the test
|
||||||
|
framework itself relies on basic instruction behavior being correct.
|
||||||
|
|
||||||
|
|
||||||
|
Internal operation
|
||||||
|
------------------
|
||||||
|
The main tests use a framework that runs each instruction in a loop,
|
||||||
|
varying the register values on input and examining them on output.
|
||||||
|
Rather than keep a table of correct values, it simply calculates a
|
||||||
|
CRC-32 checksum of all the output, then compares this with the correct
|
||||||
|
value. Instructions are divided into several groups, each with a
|
||||||
|
different set of input values suited for their behavior; for example,
|
||||||
|
the bit test instructions are fed $01, $02, $04 ... $40, $80, to ensure
|
||||||
|
each bit is handled properly, while the arithmetic instructions are fed
|
||||||
|
$01, $0F, $10, $7F, $FF, to exercise carry and half-carry. A few
|
||||||
|
instructions require a custom test due to their uniqueness.
|
||||||
|
|
||||||
|
|
||||||
|
Multi-ROM
|
||||||
|
---------
|
||||||
|
In the main directory is a single ROM which runs all the tests. It
|
||||||
|
prints a test's number, runs the test, then "ok" if it passes, otherwise
|
||||||
|
a failure code. Once all tests have completed it either reports that all
|
||||||
|
tests passed, or prints the number of failed tests. Finally, it makes
|
||||||
|
several beeps. If a test fails, it can be run on its own by finding the
|
||||||
|
corresponding ROM in individual/.
|
||||||
|
|
||||||
|
Ths compact format on screen is to avoid having the results scroll off
|
||||||
|
the top, so the test can be started and allowed to run without having to
|
||||||
|
constantly monitor the display.
|
||||||
|
|
||||||
|
Currently there is no well-defined way for an emulator test rig to
|
||||||
|
programatically find the result of the test; contact me if you're trying
|
||||||
|
to do completely automated testing of your emulator. One simple approach
|
||||||
|
is to take a screenshot after all tests have run, or even just a
|
||||||
|
checksum of one, and compare this with a previous run.
|
||||||
|
|
||||||
|
|
||||||
|
Failure codes
|
||||||
|
-------------
|
||||||
|
Failed tests may print a failure code, and also short description of the
|
||||||
|
problem. For more information about a failure code, look in the
|
||||||
|
corresponding source file in source/; the point in the code where
|
||||||
|
"set_test n" occurs is where that failure code will be generated.
|
||||||
|
Failure code 1 is a general failure of the test; any further information
|
||||||
|
will be printed.
|
||||||
|
|
||||||
|
Note that once a sub-test fails, no further tests for that file are run.
|
||||||
|
|
||||||
|
|
||||||
|
Console output
|
||||||
|
--------------
|
||||||
|
Information is printed on screen in a way that needs only minimum LCD
|
||||||
|
support, and won't hang if LCD output isn't supported at all.
|
||||||
|
Specifically, while polling LY to wait for vblank, it will time out if
|
||||||
|
it takes too long, so LY always reading back as the same value won't
|
||||||
|
hang the test. It's also OK if scrolling isn't supported; in this case,
|
||||||
|
text will appear starting at the top of the screen.
|
||||||
|
|
||||||
|
Everything printed on screen is also sent to the game link port by
|
||||||
|
writing the character to SB, then writing $81 to SC. This is useful for
|
||||||
|
tests which print lots of information that scrolls off screen.
|
||||||
|
|
||||||
|
|
||||||
|
Source code
|
||||||
|
-----------
|
||||||
|
Source code is included for all tests, in source/. It can be used to
|
||||||
|
build the individual test ROMs. Code for the multi test isn't included
|
||||||
|
due to the complexity of putting everything together.
|
||||||
|
|
||||||
|
Code is written for the wla-dx assembler. To assemble a particular test,
|
||||||
|
execute
|
||||||
|
|
||||||
|
wla -o "source_filename.s" test.o
|
||||||
|
wlalink linkfile test.gb
|
||||||
|
|
||||||
|
Test code uses a common shell framework contained in common/.
|
||||||
|
|
||||||
|
|
||||||
|
Internal framework operation
|
||||||
|
----------------------------
|
||||||
|
Tests use a common framework for setting things up, reporting results,
|
||||||
|
and ending. All files first include "shell.inc", which sets up the ROM
|
||||||
|
header and shell code, and includes other commonly-used modules.
|
||||||
|
|
||||||
|
One oddity is that test code is first copied to internal RAM at $D000,
|
||||||
|
then executed there. This allows self-modification, and ensures the code
|
||||||
|
is executed the same way it is on my devcart, which doesn't have a
|
||||||
|
rewritable ROM as most do.
|
||||||
|
|
||||||
|
Some macros are used to simplify common tasks:
|
||||||
|
|
||||||
|
Macro Behavior
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
wreg addr,data Writes data to addr using LDH
|
||||||
|
lda addr Loads byte from addr into A using LDH
|
||||||
|
sta addr Stores A at addr using LDH
|
||||||
|
delay n Delays n cycles, where NOP = 1 cycle
|
||||||
|
delay_msec n Delays n milliseconds
|
||||||
|
set_test n,"Cause" Sets failure code and optional string
|
||||||
|
|
||||||
|
Routines and macros are documented where they are defined.
|
||||||
|
|
||||||
|
--
|
||||||
|
Shay Green <gblargg@gmail.com>
|
||||||
Loading…
Reference in New Issue
Block a user