diff --git a/src/cartridge.rs b/src/cartridge.rs index 504459d..9b7bd9e 100644 --- a/src/cartridge.rs +++ b/src/cartridge.rs @@ -85,9 +85,9 @@ impl Cartridge { match addr { 0x0000 ... 0x3FFF => self.rom[addr as usize], 0x4000 ... 0x7FFF => { + let addr = addr - 0x4000; let abs_addr: usize = addr as usize + self.bank_no as usize * 0x4000; let val: u8 = self.rom[abs_addr]; - // println!("Access {:04X}{:04X} = {:02X}", self.bank_no, addr, val); val }, 0xA000 ... 0xBFFF => { @@ -143,7 +143,7 @@ impl Cartridge { pub fn write_byte(&mut self, addr: u16, val: u8) { 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), _ => panic!("MBC not supported.") } diff --git a/src/cpu.rs b/src/cpu.rs index f089a19..d816933 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -36,6 +36,8 @@ pub struct CPU { ime: bool, debug: bool, + + halted: bool, } fn to_u16(bytes: Box<[u8]>) -> u16 { @@ -52,6 +54,7 @@ impl CPU { interconnect: interconnect, ime: false, // Is this correct? debug: false, + halted: false, } } @@ -125,6 +128,30 @@ impl CPU { self.clear_flag(FLAG_N); 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 => { let reg_id = (instruction - 0x30) as usize; if self.debug { @@ -132,7 +159,15 @@ impl CPU { } let v: u8 = self.get_8bit_reg(reg_id); 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 => { // Test 0th bit let reg_id = (instruction - 0x40) as usize; @@ -362,10 +397,9 @@ impl CPU { } let val = self.get_8bit_reg(reg_id).wrapping_add(1); self.set_8bit_reg(reg_id, val); - if val == 0 { - self.flags |= FLAG_Z; - } - self.flags &= !FLAG_N; + self.set_clear_flag(FLAG_Z, val == 0); + self.set_clear_flag(FLAG_C, val == 0); + self.clear_flag(FLAG_N); 4 } @@ -382,12 +416,10 @@ impl CPU { } let val = self.get_8bit_reg(reg_id).wrapping_sub(1); self.set_8bit_reg(reg_id, val); - if val == 0 { - self.flags |= FLAG_Z; - } else { - self.flags &= !FLAG_Z; - } - self.flags |= FLAG_N; + + self.set_clear_flag(FLAG_Z, val == 0); + self.set_clear_flag(FLAG_C, val == 255); + self.set_flag(FLAG_N); 4 } @@ -409,6 +441,14 @@ impl CPU { 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){ if self.debug { println!("INT {:02X}", val); @@ -429,6 +469,16 @@ impl CPU { 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) { // self.debug = !self.interconnect.is_boot_rom(); // Check for interrupts. @@ -441,645 +491,745 @@ impl CPU { if e_pending & interconnect::INTERRUPT_DISPLAY_VBLANK > 0 { println!("Handling vblank interrupt"); - self.interconnect.write_byte(0xFF0F, pending & !interconnect::INTERRUPT_DISPLAY_VBLANK); - self.int_(0x40); - return; + self.handle_interrupt(0x40, interconnect::INTERRUPT_DISPLAY_VBLANK); } else if e_pending & interconnect::INTERRUPT_DISPLAY_STAT > 0 { println!("Handling display stat interrupt"); - self.interconnect.write_byte(0xFF0F, pending & !interconnect::INTERRUPT_DISPLAY_STAT); - self.int_(0x48); - return; + self.handle_interrupt(0x40, interconnect::INTERRUPT_DISPLAY_STAT); } else if e_pending > 0 { panic!("Unknown pending interrupt: {:02X}", e_pending); } } - // We need to double-check the flags - let instruction = self.read_byte(self.ip); - if self.debug { - print!("{:#04x}: [SP: {:#04X}] i={:02X}. ", &self.ip, &self.sp, &instruction); - for i in 0 .. 6 { - print!("{}: {:02X} ", REG_NAMES[i], self.get_8bit_reg(i)); + + let mut cycles: u8 = 1; + if !self.halted { + // We need to double-check the flags + let instruction = self.read_byte(self.ip); + if self.debug { + print!("{:#04x}: [SP: {:#04X}] i={:02X}. ", &self.ip, &self.sp, &instruction); + for i in 0 .. 6 { + print!("{}: {:02X} ", REG_NAMES[i], self.get_8bit_reg(i)); + } + print!("A: {:02X} ", self.regs[REG_A]); + print!("I: {:02X} ", self.interconnect.read_byte(0xFFFF)); + + // Flags + print!("Z={} ", self.flags & FLAG_Z != 0); + print!("N={} ", self.flags & FLAG_N != 0); + print!("H={} ", self.flags & FLAG_H != 0); + print!("C={} ", self.flags & FLAG_C != 0); } - print!("A: {:02X} ", self.regs[REG_A]); - print!("I: {:02X} ", self.interconnect.read_byte(0xFFFF)); - // Flags - print!("Z={} ", self.flags & FLAG_Z != 0); - print!("N={} ", self.flags & FLAG_N != 0); - print!("H={} ", self.flags & FLAG_H != 0); - print!("C={} ", self.flags & FLAG_C != 0); - } + self.ip = self.ip.wrapping_add(1); - self.ip = self.ip.wrapping_add(1); - - let cycles: u8 = match instruction { - 0x00 => { - if self.debug { - println!("NOP"); - } - 4 - }, - 0x01 => self.ld_rr_vv(REG_N_B, REG_N_C), - 0x02 => self.ld_dref_rr_a(REG_N_B, REG_N_C), - 0x04 => self.reg_inc(REG_N_B), - 0x05 => self.reg_dec(REG_N_B), - 0x06 => self.ld_r_v(REG_N_B), - - 0x08 => { - let a: u16 = to_u16(self.load_args(2)); - if self.debug{ - println!("LD ({:04X}), sp", a); - } - self.interconnect.write_word(a, self.sp); - 20 - } - 0x09 => self.add_rr_rr(REG_N_H, REG_N_L, REG_N_B, REG_N_C), - 0x0B => self.dec_rr(REG_N_B, REG_N_C), - 0x0C => self.reg_inc(REG_N_C), - 0x0D => self.reg_dec(REG_N_C), - 0x0E => self.ld_r_v(REG_N_C), - 0x11 => self.ld_rr_vv(REG_N_D, REG_N_E), - 0x12 => self.ld_dref_rr_a(REG_D, REG_E), - 0x13 => self.inc_rr(REG_D, REG_E), - 0x14 => self.reg_inc(REG_N_D), - 0x15 => self.reg_dec(REG_N_D), - 0x16 => self.ld_r_v(REG_N_D), - 0x17 => { - if self.debug { - println!("RLA"); - } - let carry = self.flags & FLAG_C > 0; - if !carry { - // No carry before, now we got a carry => set it - if self.regs[REG_A] & 0x80 == 0x80 { - self.set_flag(FLAG_C); + cycles = match instruction { + 0x00 => { + if self.debug { + println!("NOP"); } - self.regs[REG_A] = self.regs[REG_A] << 1; - } else { - if self.regs[REG_A] & 0x80 == 0 { - self.clear_flag(FLAG_C); + 4 + }, + 0x01 => self.ld_rr_vv(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), + 0x05 => self.reg_dec(REG_N_B), + 0x06 => self.ld_r_v(REG_N_B), + + 0x08 => { + let a: u16 = to_u16(self.load_args(2)); + if self.debug{ + println!("LD ({:04X}), sp", a); } - self.regs[REG_A] = self.regs[REG_A] << 1 | 1; - } - self.clear_flag(FLAG_Z); - self.clear_flag(FLAG_N); - self.clear_flag(FLAG_H); - 4 - }, - 0x18 => { - let args = self.load_args(1); - if self.debug { - println!("JR {:02X}", args[0] as i8); - } - let off: i8 = args[0] as i8; - if off < 0 { - self.ip -= (-off) as u16; - } else { - self.ip += off as u16; - } - 12 - }, - 0x19 => self.add_rr_rr(REG_N_H, REG_N_L, REG_N_D, REG_N_E), - 0x1A => { - if self.debug { - println!("LD A, (DE)"); - } - self.regs[REG_A] = self.interconnect.read_byte(self.get_pair_value(REG_D, REG_E)); - 8 - }, - 0x1B => self.dec_rr(REG_N_D, REG_N_E), - 0x1C => self.reg_inc(REG_N_E), - 0x1D => self.reg_dec(REG_N_E), - 0x1E => self.ld_r_v(REG_N_E), - 0x20 => { - let args = self.load_args(1); - if self.debug { - println!("JR NZ {:02x}", args[0] as i8); - } - if self.flags & FLAG_Z == 0 { - let offset = args[0] as i8; - if offset < 0 { - self.ip -= (-offset) as u16 - } else { - self.ip += offset as u16; - } - 12 - } else { - 8 - } - } - 0x21 => self.ld_rr_vv(REG_N_H, REG_N_L), - 0x22 => { - if self.debug { - println!("LD (HL+), A"); - } - let addr: u16 = self.get_pair_value(REG_H, REG_L); - self.interconnect.write_byte(addr, self.regs[REG_A]); - self.set_pair_value(REG_H, REG_L, addr + 1); - 8 - }, - 0x23 => self.inc_rr(REG_H, REG_L), - 0x24 => self.reg_inc(REG_N_H), - 0x25 => self.reg_dec(REG_N_H), - 0x26 => self.ld_r_v(REG_N_H), - 0x28 => { - let args = self.load_args(1); - let target; - if (args[0] as i8) < 0 { - target = self.ip - (-(args[0] as i8)) as u16; - } else { - target = self.ip + args[0] as u16; - } - if self.debug { - println!("JR Z, {:04X}", target); - } - if self.flags & FLAG_Z > 0 { - self.ip = target; - 12 - } else { - 8 - } - }, - 0x29 => self.add_rr_rr(REG_N_H, REG_N_L, REG_N_H, REG_N_L), - 0x2A => { - if self.debug { - println!("LD A, (HL+)"); - } - let addr: u16 = self.get_pair_value(REG_H, REG_L); - self.regs[REG_A] = self.interconnect.read_byte(addr); - 8 - }, - 0x2B => self.dec_rr(REG_N_H, REG_N_L), - 0x2C => self.reg_inc(REG_N_L), - 0x2D => self.reg_dec(REG_N_L), - 0x2E => self.ld_r_v(REG_N_L), - 0x2F => { - if self.debug { - println!("CPL"); - } - self.regs[REG_A] = !self.regs[REG_A]; - 4 - }, - 0x30 => { - let args = self.load_args(1); - if self.debug { - println!("JR NC {:02x}", args[0] as i8); - } - if self.flags & FLAG_C == 0 { - let offset = args[0] as i8; - if offset < 0 { - self.ip -= (-offset) as u16 - } else { - self.ip += offset as u16; - } - 12 - } else { - 8 - } - }, - 0x31 => { - let args = self.load_args(2); - self.sp = to_u16(args); - if self.debug { - println!("LD SP, {:04x}", self.sp); - } - 12 - }, - 0x32 => { - if self.debug { - println!("LD (HL-), A"); - } - let mut addr = self.get_pair_value(REG_H, REG_L); - self.interconnect.write_byte(addr, self.regs[REG_A]); - - addr -= 1; - self.set_pair_value(REG_H, REG_L, addr); - 8 - }, - 0x36 => { - let args = self.load_args(1); - if self.debug { - println!("LD (HL), {:02x}", args[0]); - } - let addr = self.get_pair_value(REG_H, REG_L); - self.interconnect.write_byte(addr, args[0]); - 12 - }, - 0x37 => { - if self.debug { - println!("SCF"); - } - self.set_flag(FLAG_C); - 4 - }, - 0x38 => { - let args = self.load_args(1); - if self.debug { - println!("JR C {:02x}", args[0] as i8); - } - if self.flags & FLAG_C > 0 { - let offset = args[0] as i8; - if offset < 0 { - self.ip -= (-offset) as u16 - } else { - self.ip += offset as u16; - } - 12 - } else { - 8 - } - }, - 0x39 => { - if self.debug { - println!("ADD HL, SP"); - } - let sp = self.sp; - let v = self.get_pair_value(REG_N_H, REG_N_L).wrapping_add(sp); - self.set_pair_value(REG_N_H, REG_N_L, v); - 8 - } - 0x3C => self.reg_inc(REG_N_A), - 0x3D => self.reg_dec(REG_N_A), - 0x3E => self.ld_r_v(REG_N_A), - 0x3F => { - if self.debug { - println!("CCF"); - } - self.flags ^= FLAG_C; - 4 - } - - // LDs - 0x40 ... 0x47 => self.ld_r_r(REG_N_B, (instruction - 0x40) as usize), - 0x48 ... 0x4F => self.ld_r_r(REG_N_C, (instruction - 0x48) as usize), - 0x50 ... 0x57 => self.ld_r_r(REG_N_D, (instruction - 0x50) as usize), - 0x58 ... 0x5F => self.ld_r_r(REG_N_E, (instruction - 0x58) as usize), - 0x60 ... 0x67 => self.ld_r_r(REG_N_H, (instruction - 0x60) as usize), - 0x68 ... 0x6F => self.ld_r_r(REG_N_L, (instruction - 0x68) 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), - - // ADD - 0x80 ... 0x87 => { - let reg_id = (instruction - 0x80) as usize; - if self.debug { - println!("ADD {}", REG_NAMES[reg_id]); - } - self.regs[REG_A] = self.regs[REG_A].wrapping_add(self.get_8bit_reg(reg_id)); - - self.clear_flag(FLAG_N); - 4 - } - - // ADC - 0x88 ... 0x8F => { - let reg_id = (instruction - 0x88) as usize; - println!("ADC {}", REG_NAMES[reg_id]); - self.regs[REG_A] = self.regs[REG_A].wrapping_add(self.get_8bit_reg(reg_id)); - if self.flags & FLAG_C == FLAG_C { - self.regs[REG_A] = self.regs[REG_A].wrapping_add(1); - } - - self.set_flag(FLAG_N); - 4 - } - - // SUBs - 0x90 ... 0x97 => { - let reg_id = (instruction - 0x90) as usize; - if self.debug { - println!("SUB {}", REG_NAMES[reg_id]); - } - self.regs[REG_A] = self.regs[REG_A].wrapping_sub(self.get_8bit_reg(reg_id)); - self.set_flag(FLAG_N); - if self.regs[REG_A] == 0 { - self.set_flag(FLAG_Z); - } else { - self.clear_flag(FLAG_Z); - } - // TODO: H, C - 4 - } - - // SBC - 0x98 ... 0x9F => { - let reg_id = (instruction - 0x98) as usize; - if self.debug { - println!("SBC {}", REG_NAMES[reg_id]); - } - self.regs[REG_A] = self.regs[REG_A].wrapping_sub(self.get_8bit_reg(reg_id)); - if self.flags & FLAG_C == FLAG_C { - self.regs[REG_A] = self.regs[REG_A].wrapping_sub(1); - } - self.set_flag(FLAG_N); - if self.regs[REG_A] == 0 { - self.set_flag(FLAG_Z); - } else { - self.clear_flag(FLAG_Z); - } - 4 - } - - // AND - 0xA0 ... 0xA7 => { - let reg_id = (instruction - 0xA0) as usize; - if self.debug { - println!("AND {}", REG_NAMES[reg_id]); - } - self.regs[REG_A] &= self.get_8bit_reg(reg_id); - self.clear_flag(FLAG_N); - if self.regs[REG_A] == 0 { - self.set_flag(FLAG_Z); - } else { - self.clear_flag(FLAG_Z); - } - 4 - } - - // XOR - 0xA8 ... 0xAF => { - let reg_id = (instruction - 0xA8) as usize; - if self.debug { - println!("XOR {}", REG_NAMES[reg_id]); - } - self.regs[REG_A] ^= self.get_8bit_reg(reg_id); - if self.regs[REG_A] == 0 { - self.set_flag(FLAG_Z); - } else { - self.set_flag(FLAG_Z); - } - - self.clear_flag(FLAG_C); - self.clear_flag(FLAG_N); - self.clear_flag(FLAG_H); - 4 - }, - - // OR - 0xB0 ... 0xB7 => { - let reg_id = (instruction - 0xB0) as usize; - if self.debug { - println!("OR {}", REG_NAMES[reg_id]); - } - self.regs[REG_A] |= self.get_8bit_reg(reg_id); - self.clear_flag(FLAG_N); - if self.regs[REG_A] == 0 { - self.set_flag(FLAG_Z); - } else { - self.clear_flag(FLAG_Z); - } - 4 - } - - // CP - 0xB8 ... 0xBF => { - let reg_id = (instruction - 0xB8) as usize; - if self.debug { - println!("CP {}", REG_NAMES[reg_id]); - } - let val = self.get_8bit_reg(reg_id); - - self.set_flag(FLAG_N); - if self.regs[REG_A] < val { - self.set_flag(FLAG_C); - 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 - }, - 0xC0 => { - if self.debug { - println!("RET NZ"); - } - if self.flags & FLAG_Z == 0 { - self.ret(); + self.interconnect.write_word(a, self.sp); 20 - } else { + } + 0x09 => self.add_rr_rr(REG_N_H, REG_N_L, REG_N_B, REG_N_C), + 0x0B => self.dec_rr(REG_N_B, REG_N_C), + 0x0C => self.reg_inc(REG_N_C), + 0x0D => self.reg_dec(REG_N_C), + 0x0E => self.ld_r_v(REG_N_C), + 0x11 => self.ld_rr_vv(REG_N_D, REG_N_E), + 0x12 => self.ld_dref_rr_a(REG_D, REG_E), + 0x13 => self.inc_rr(REG_D, REG_E), + 0x14 => self.reg_inc(REG_N_D), + 0x15 => self.reg_dec(REG_N_D), + 0x16 => self.ld_r_v(REG_N_D), + 0x17 => { + if self.debug { + println!("RLA"); + } + let carry = self.flags & FLAG_C > 0; + if !carry { + // No carry before, now we got a carry => set it + if self.regs[REG_A] & 0x80 == 0x80 { + self.set_flag(FLAG_C); + } + self.regs[REG_A] = self.regs[REG_A] << 1; + } else { + if self.regs[REG_A] & 0x80 == 0 { + self.clear_flag(FLAG_C); + } + self.regs[REG_A] = self.regs[REG_A] << 1 | 1; + } + self.clear_flag(FLAG_Z); + self.clear_flag(FLAG_N); + self.clear_flag(FLAG_H); + 4 + }, + 0x18 => { + let args = self.load_args(1); + if self.debug { + println!("JR {:02X}", args[0] as i8); + } + let off: i8 = args[0] as i8; + if off < 0 { + self.ip -= (-off) as u16; + } else { + self.ip += off as u16; + } + 12 + }, + 0x19 => self.add_rr_rr(REG_N_H, REG_N_L, REG_N_D, REG_N_E), + 0x1A => { + if self.debug { + println!("LD A, (DE)"); + } + self.regs[REG_A] = self.interconnect.read_byte(self.get_pair_value(REG_D, REG_E)); + 8 + }, + 0x1B => self.dec_rr(REG_N_D, REG_N_E), + 0x1C => self.reg_inc(REG_N_E), + 0x1D => self.reg_dec(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 => { + let args = self.load_args(1); + if self.debug { + println!("JR NZ {:02x}", args[0] as i8); + } + if self.flags & FLAG_Z == 0 { + let offset = args[0] as i8; + if offset < 0 { + self.ip -= (-offset) as u16 + } else { + self.ip += offset as u16; + } + 12 + } else { + 8 + } + } + 0x21 => self.ld_rr_vv(REG_N_H, REG_N_L), + 0x22 => { + if self.debug { + println!("LD (HL+), A"); + } + let addr: u16 = self.get_pair_value(REG_H, REG_L); + self.interconnect.write_byte(addr, self.regs[REG_A]); + self.set_pair_value(REG_H, REG_L, addr.wrapping_add(1)); + 8 + }, + 0x23 => self.inc_rr(REG_H, REG_L), + 0x24 => self.reg_inc(REG_N_H), + 0x25 => self.reg_dec(REG_N_H), + 0x26 => self.ld_r_v(REG_N_H), + 0x28 => { + let args = self.load_args(1); + let target; + if (args[0] as i8) < 0 { + target = self.ip - (-(args[0] as i8)) as u16; + } else { + target = self.ip + args[0] as u16; + } + if self.debug { + println!("JR Z, {:04X}", target); + } + if self.flags & FLAG_Z > 0 { + self.ip = target; + 12 + } else { + 8 + } + }, + 0x29 => self.add_rr_rr(REG_N_H, REG_N_L, REG_N_H, REG_N_L), + 0x2A => { + if self.debug { + println!("LD A, (HL+)"); + } + let addr: u16 = self.get_pair_value(REG_H, REG_L); + self.regs[REG_A] = self.interconnect.read_byte(addr); + self.set_pair_value(REG_H, REG_L, addr+1); + 8 + }, + 0x2B => self.dec_rr(REG_N_H, REG_N_L), + 0x2C => self.reg_inc(REG_N_L), + 0x2D => self.reg_dec(REG_N_L), + 0x2E => self.ld_r_v(REG_N_L), + 0x2F => { + if self.debug { + println!("CPL"); + } + self.regs[REG_A] = !self.regs[REG_A]; + 4 + }, + 0x30 => { + let args = self.load_args(1); + if self.debug { + println!("JR NC {:02x}", args[0] as i8); + } + if self.flags & FLAG_C == 0 { + let offset = args[0] as i8; + if offset < 0 { + self.ip -= (-offset) as u16 + } else { + self.ip += offset as u16; + } + 12 + } else { + 8 + } + }, + 0x31 => { + let args = self.load_args(2); + self.sp = to_u16(args); + if self.debug { + println!("LD SP, {:04x}", self.sp); + } + 12 + }, + 0x32 => { + if self.debug { + println!("LD (HL-), A"); + } + let mut addr = self.get_pair_value(REG_H, REG_L); + self.interconnect.write_byte(addr, self.regs[REG_A]); + + addr -= 1; + self.set_pair_value(REG_H, REG_L, addr); + 8 + }, + 0x35 => self.reg_dec(REG_N_HL), + 0x36 => { + let args = self.load_args(1); + if self.debug { + println!("LD (HL), {:02x}", args[0]); + } + let addr = self.get_pair_value(REG_H, REG_L); + self.interconnect.write_byte(addr, args[0]); + 12 + }, + 0x37 => { + if self.debug { + println!("SCF"); + } + self.set_flag(FLAG_C); + 4 + }, + 0x38 => { + let args = self.load_args(1); + if self.debug { + println!("JR C {:02x}", args[0] as i8); + } + if self.flags & FLAG_C > 0 { + let offset = args[0] as i8; + if offset < 0 { + self.ip -= (-offset) as u16 + } else { + self.ip += offset as u16; + } + 12 + } else { + 8 + } + }, + 0x39 => { + if self.debug { + println!("ADD HL, SP"); + } + let sp = self.sp; + let v = self.get_pair_value(REG_N_H, REG_N_L).wrapping_add(sp); + self.set_pair_value(REG_N_H, REG_N_L, v); 8 } - }, - 0xC1 => self.pop_rr(REG_B, REG_C), - 0xC2 => { - let addr: u16 = to_u16(self.load_args(2)); - if self.debug { - println!("JP NZ {:04X}", addr); + 0x3C => self.reg_inc(REG_N_A), + 0x3D => self.reg_dec(REG_N_A), + 0x3E => self.ld_r_v(REG_N_A), + 0x3F => { + if self.debug { + println!("CCF"); + } + self.flags ^= FLAG_C; + 4 } - if self.flags & FLAG_Z == 0 { + + // LDs + 0x40 ... 0x47 => self.ld_r_r(REG_N_B, (instruction - 0x40) as usize), + 0x48 ... 0x4F => self.ld_r_r(REG_N_C, (instruction - 0x48) as usize), + 0x50 ... 0x57 => self.ld_r_r(REG_N_D, (instruction - 0x50) as usize), + 0x58 ... 0x5F => self.ld_r_r(REG_N_E, (instruction - 0x58) as usize), + 0x60 ... 0x67 => self.ld_r_r(REG_N_H, (instruction - 0x60) as usize), + 0x68 ... 0x6F => self.ld_r_r(REG_N_L, (instruction - 0x68) 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), + + // HALT + 0x76 => { + if self.debug { + println!("HALT"); + } + self.halted = true; + 4 + }, + + // ADD + 0x80 ... 0x87 => { + let reg_id = (instruction - 0x80) as usize; + if self.debug { + println!("ADD {}", REG_NAMES[reg_id]); + } + self.regs[REG_A] = self.regs[REG_A].wrapping_add(self.get_8bit_reg(reg_id)); + + self.clear_flag(FLAG_N); + 4 + } + + // ADC + 0x88 ... 0x8F => { + let reg_id = (instruction - 0x88) as usize; + println!("ADC {}", REG_NAMES[reg_id]); + self.regs[REG_A] = self.regs[REG_A].wrapping_add(self.get_8bit_reg(reg_id)); + if self.flags & FLAG_C == FLAG_C { + self.regs[REG_A] = self.regs[REG_A].wrapping_add(1); + } + + self.set_flag(FLAG_N); + 4 + } + + // SUBs + 0x90 ... 0x97 => { + let reg_id = (instruction - 0x90) as usize; + if self.debug { + println!("SUB {}", REG_NAMES[reg_id]); + } + self.regs[REG_A] = self.regs[REG_A].wrapping_sub(self.get_8bit_reg(reg_id)); + self.set_flag(FLAG_N); + if self.regs[REG_A] == 0 { + self.set_flag(FLAG_Z); + } else { + self.clear_flag(FLAG_Z); + } + // TODO: H, C + 4 + } + + // SBC + 0x98 ... 0x9F => { + let reg_id = (instruction - 0x98) as usize; + if self.debug { + println!("SBC {}", REG_NAMES[reg_id]); + } + self.regs[REG_A] = self.regs[REG_A].wrapping_sub(self.get_8bit_reg(reg_id)); + if self.flags & FLAG_C == FLAG_C { + self.regs[REG_A] = self.regs[REG_A].wrapping_sub(1); + } + self.set_flag(FLAG_N); + if self.regs[REG_A] == 0 { + self.set_flag(FLAG_Z); + } else { + self.clear_flag(FLAG_Z); + } + 4 + } + + // AND + 0xA0 ... 0xA7 => { + let reg_id = (instruction - 0xA0) as usize; + if self.debug { + println!("AND {}", REG_NAMES[reg_id]); + } + self.regs[REG_A] &= self.get_8bit_reg(reg_id); + self.clear_flag(FLAG_N); + if self.regs[REG_A] == 0 { + self.set_flag(FLAG_Z); + } else { + self.clear_flag(FLAG_Z); + } + 4 + } + + // XOR + 0xA8 ... 0xAF => { + let reg_id = (instruction - 0xA8) as usize; + if self.debug { + println!("XOR {}", REG_NAMES[reg_id]); + } + self.regs[REG_A] ^= self.get_8bit_reg(reg_id); + if self.regs[REG_A] == 0 { + self.set_flag(FLAG_Z); + } else { + self.set_flag(FLAG_Z); + } + + self.clear_flag(FLAG_C); + self.clear_flag(FLAG_N); + self.clear_flag(FLAG_H); + 4 + }, + + // OR + 0xB0 ... 0xB7 => { + let reg_id = (instruction - 0xB0) as usize; + if self.debug { + println!("OR {}", REG_NAMES[reg_id]); + } + self.regs[REG_A] |= self.get_8bit_reg(reg_id); + self.clear_flag(FLAG_N); + if self.regs[REG_A] == 0 { + self.set_flag(FLAG_Z); + } else { + self.clear_flag(FLAG_Z); + } + 4 + } + + // CP + 0xB8 ... 0xBF => { + let reg_id = (instruction - 0xB8) as usize; + if self.debug { + println!("CP {}", REG_NAMES[reg_id]); + } + let val = self.get_8bit_reg(reg_id); + let rval = self.regs[REG_A]; + + self.set_flag(FLAG_N); + self.set_clear_flag(FLAG_C, rval < val); + self.set_clear_flag(FLAG_Z, rval == val); + 4 + }, + 0xC0 => { + if self.debug { + println!("RET NZ"); + } + if self.flags & FLAG_Z == 0 { + self.ret(); + 20 + } else { + 8 + } + }, + 0xC1 => self.pop_rr(REG_B, REG_C), + 0xC2 => { + let addr: u16 = to_u16(self.load_args(2)); + if self.debug { + println!("JP NZ {:04X}", addr); + } + if self.flags & FLAG_Z == 0 { + self.ip = addr; + 16 + } else { + 12 + } + }, + 0xC3 => { + let addr: u16 = to_u16(self.load_args(2)); + if self.debug { + println!("JP {:04X}", addr); + } self.ip = addr; 16 - } else { - 12 } - }, - 0xC3 => { - let addr: u16 = to_u16(self.load_args(2)); - if self.debug { - println!("JP {:04X}", addr); - } - self.ip = addr; - 16 - } - 0xC4 => { - let target = to_u16(self.load_args(2)); - if self.debug { - println!("CALL NZ {:04X}", &target); - } - if self.flags & FLAG_Z == 0 { - self.call(target); - 24 - } else { - 12 - } - }, - 0xC5 => self.push_rr(REG_B, REG_C), - 0xC6 => { - let val = self.load_args(1)[0]; - if self.debug { - println!("ADD {:02X}", val); - } - self.regs[REG_A] = self.regs[REG_A].wrapping_add(val); - 8 - }, - 0xC7 => self.rst(0x00), - 0xC8 => { - if self.debug { - println!("RET Z"); - } - if self.flags & FLAG_Z > 0 { - self.ret(); - 20 - } else { + 0xC4 => { + let target = to_u16(self.load_args(2)); + if self.debug { + println!("CALL NZ {:04X}", &target); + } + if self.flags & FLAG_Z == 0 { + self.call(target); + 24 + } else { + 12 + } + }, + 0xC5 => self.push_rr(REG_B, REG_C), + 0xC6 => { + let val = self.load_args(1)[0]; + if self.debug { + println!("ADD {:02X}", val); + } + self.regs[REG_A] = self.regs[REG_A].wrapping_add(val); 8 - } - }, - 0xC9 => { - if self.debug { - println!("RET"); - } - let new_ip: u16 = self.pop(); - self.ip = new_ip; - 16 - }, - 0xCB => { - // Prefix CB. This is annoying. - self.run_prefix_instruction(); - 12 // TODO: Verify that this is the case for all prefix instructions. - }, - 0xCC => { - let target = to_u16(self.load_args(2)); - if self.debug { - println!("CALL Z {:04X}", &target); - } - if self.flags & FLAG_Z > 0 { + }, + 0xC7 => self.rst(0x00), + 0xC8 => { + if self.debug { + println!("RET Z"); + } + if self.flags & FLAG_Z > 0 { + self.ret(); + 20 + } else { + 8 + } + }, + 0xC9 => { + if self.debug { + println!("RET"); + } + let new_ip: u16 = self.pop(); + self.ip = new_ip; + 16 + }, + 0xCB => { + // Prefix CB. This is annoying. + self.run_prefix_instruction(); + 12 // TODO: Verify that this is the case for all prefix instructions. + }, + 0xCC => { + let target = to_u16(self.load_args(2)); + if self.debug { + println!("CALL Z {:04X}", &target); + } + if self.flags & FLAG_Z > 0 { + self.call(target); + 24 + } else { + 12 + } + }, + 0xCD => { + let target = to_u16(self.load_args(2)); + if self.debug { + println!("CALL {:04X}", &target); + } self.call(target); 24 - } else { - 12 - } - }, - 0xCD => { - let target = to_u16(self.load_args(2)); - if self.debug { - println!("CALL {:04X}", &target); - } - self.call(target); - 24 - }, - 0xCF => self.rst(0x08), - 0xD1 => self.pop_rr(REG_D, REG_E), - 0xD5 => self.push_rr(REG_D, REG_E), - 0xD6 => { - let val = self.load_args(1)[0]; - if self.debug { - println!("SUB {:02X}", val); - } - self.regs[REG_A] = self.regs[REG_A].wrapping_sub(val); - 8 - }, - 0xD7 => self.rst(0x10), - 0xDF => self.rst(0x18), - 0xE0 => { - let args = self.load_args(1); - if self.debug { - println!("LDH {:02X}, A", args[0]); - } - self.interconnect.write_byte(0xFF00 + args[0] as u16, self.regs[REG_A]); - 12 - }, - 0xE1 => self.pop_rr(REG_N_H, REG_N_L), - 0xE2 => { - if self.debug { - println!("LD (C), A"); - println!("[{:04X}] = {:02X}", 0xFF00 + self.regs[REG_C] as u16, self.regs[REG_A]); - } - self.interconnect.write_byte(0xFF00 + self.regs[REG_C] as u16, self.regs[REG_A]); - 8 - }, - 0xE5 => self.push_rr(REG_H, REG_L), - 0xE6 => { - let val = self.load_args(1)[0]; - if self.debug { - println!("AND {:02X}", val); - } - self.regs[REG_A] &= val; - 8 - }, - 0xE7 => self.rst(0x20), - 0xE9 => { - if self.debug { - println!("JP (HL)"); - } - let new_ip = (self.ip as i16 + self.get_8bit_reg(REG_N_HL) as i16) as u16; - self.ip = new_ip; - 4 - }, - 0xEA => { - let addr = to_u16(self.load_args(2)); - if self.debug{ - println!("LD ({:04X}), A", addr); - } - self.interconnect.write_byte(addr, self.regs[REG_A]); - 16 - }, - 0xEF => self.rst(0x28), - 0xF0 => { - let args = self.load_args(1); - if self.debug { - println!("LDH A, {:02X}", args[0]); - } - self.regs[REG_A] = self.interconnect.read_byte(0xFF00 + args[0] as u16); - 12 - }, - 0xF1 => self.pop_rr(REG_N_A, REG_N_F), - 0xF2 => { - if self.debug{ - println!("LD A, (C)"); - } - self.regs[REG_A] = self.interconnect.read_byte(0xFF00 + self.regs[REG_C] as u16); - 8 - }, - 0xF3 => { - if self.debug { - println!("DI"); - } - self.ime = false; - 4 - }, - 0xF5 => self.push_rr(REG_N_A, REG_N_F), - 0xF6 => { - let val = self.load_args(1)[0]; - if self.debug { - println!("OR {:02X}", val); - } - self.regs[REG_A] |= val; - 8 - }, - 0xF7 => self.rst(0x30), - 0xFB => { - // Enable interrupts - TODO - if self.debug { - println!("EI"); - } - self.ime = true; // interrupt master enable - 4 - }, - 0xFA => { - let addr: u16 = to_u16(self.load_args(2)); - if self.debug { - println!("LD A, ({:04X})", addr); - } - let val: u8 = self.interconnect.read_byte(addr); - self.set_8bit_reg(REG_N_A, val); - 16 - }, - 0xFE => { - let args = self.load_args(1); - if self.debug { - println!("CP {:02X}", args[0]); - } - self.set_flag(FLAG_N); - if args[0] > self.regs[REG_A] { - self.set_flag(FLAG_C); - } else { - self.clear_flag(FLAG_C); - } - if args[0] == self.regs[REG_A] { - self.set_flag(FLAG_Z); - } else { - self.clear_flag(FLAG_Z); - } + }, + 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), + 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), + 0xD5 => self.push_rr(REG_D, REG_E), + 0xD6 => { + let val = self.load_args(1)[0]; + if self.debug { + println!("SUB {:02X}", val); + } - // TODO H - 8 - }, - 0xFF => self.rst(0x38), - _ => panic!("Unknown instruction: {:02x}", instruction) - }; + 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 + }, + 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), + 0xE0 => { + let args = self.load_args(1); + if self.debug { + println!("LDH {:02X}, A", args[0]); + } + self.interconnect.write_byte(0xFF00 + args[0] as u16, self.regs[REG_A]); + 12 + }, + 0xE1 => self.pop_rr(REG_N_H, REG_N_L), + 0xE2 => { + if self.debug { + println!("LD (C), A"); + println!("[{:04X}] = {:02X}", 0xFF00 + self.regs[REG_C] as u16, self.regs[REG_A]); + } + self.interconnect.write_byte(0xFF00 + self.regs[REG_C] as u16, self.regs[REG_A]); + 8 + }, + 0xE5 => self.push_rr(REG_H, REG_L), + 0xE6 => { + let val = self.load_args(1)[0]; + if self.debug { + println!("AND {:02X}", val); + } + self.regs[REG_A] &= val; + 8 + }, + 0xE7 => self.rst(0x20), + 0xE9 => { + if self.debug { + println!("JP (HL)"); + } + let new_ip = (self.ip as i16 + self.get_8bit_reg(REG_N_HL) as i16) as u16; + self.ip = new_ip; + 4 + }, + 0xEA => { + let addr = to_u16(self.load_args(2)); + if self.debug{ + println!("LD ({:04X}), A", addr); + } + self.interconnect.write_byte(addr, self.regs[REG_A]); + 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), + 0xF0 => { + let args = self.load_args(1); + if self.debug { + println!("LDH A, {:02X}", args[0]); + } + self.regs[REG_A] = self.interconnect.read_byte(0xFF00 + args[0] as u16); + 12 + }, + 0xF1 => self.pop_rr(REG_N_A, REG_N_F), + 0xF2 => { + if self.debug{ + println!("LD A, (C)"); + } + self.regs[REG_A] = self.interconnect.read_byte(0xFF00 + self.regs[REG_C] as u16); + 8 + }, + 0xF3 => { + if self.debug { + println!("DI"); + } + self.ime = false; + 4 + }, + 0xF5 => self.push_rr(REG_N_A, REG_N_F), + 0xF6 => { + let val = self.load_args(1)[0]; + if self.debug { + println!("OR {:02X}", val); + } + self.regs[REG_A] |= val; + 8 + }, + 0xF7 => self.rst(0x30), + 0xFB => { + // Enable interrupts - TODO + if self.debug { + println!("EI"); + } + self.ime = true; // interrupt master enable + 4 + }, + 0xFA => { + let addr: u16 = to_u16(self.load_args(2)); + if self.debug { + println!("LD A, ({:04X})", addr); + } + let val: u8 = self.interconnect.read_byte(addr); + self.set_8bit_reg(REG_N_A, val); + 16 + }, + 0xFE => { + let args = self.load_args(1); + let rval = self.regs[REG_A]; + if self.debug { + println!("CP {:02X}", args[0]); + } + self.set_flag(FLAG_N); + self.set_clear_flag(FLAG_C, args[0] > rval); + self.set_clear_flag(FLAG_Z, args[0] == rval); + + // TODO H + 8 + }, + 0xFF => self.rst(0x38), + _ => panic!("Unknown instruction: {:02x}", instruction) + }; + } // self.dump_stack(); self.interconnect.tick(cycles); } diff --git a/src/display.rs b/src/display.rs index ffd1ab3..3002992 100644 --- a/src/display.rs +++ b/src/display.rs @@ -135,7 +135,7 @@ impl Display { self.vram[(addr - 0x8000) as usize] = val; } 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; } 0xFF40 => self.control = val, diff --git a/src/interconnect.rs b/src/interconnect.rs index 95e0ba1..4b4aa0c 100644 --- a/src/interconnect.rs +++ b/src/interconnect.rs @@ -63,18 +63,6 @@ impl Interconnect { 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 { // TODO: Make this more beautiful. // TODO: if some flag set, use bios, otherwise only use rom diff --git a/tests/cpu_instrs/cpu_instrs.gb b/tests/cpu_instrs/cpu_instrs.gb new file mode 100644 index 0000000..7b06221 Binary files /dev/null and b/tests/cpu_instrs/cpu_instrs.gb differ diff --git a/tests/cpu_instrs/individual/01-special.gb b/tests/cpu_instrs/individual/01-special.gb new file mode 100644 index 0000000..ad3e998 Binary files /dev/null and b/tests/cpu_instrs/individual/01-special.gb differ diff --git a/tests/cpu_instrs/individual/02-interrupts.gb b/tests/cpu_instrs/individual/02-interrupts.gb new file mode 100644 index 0000000..2089594 Binary files /dev/null and b/tests/cpu_instrs/individual/02-interrupts.gb differ diff --git a/tests/cpu_instrs/individual/03-op sp,hl.gb b/tests/cpu_instrs/individual/03-op sp,hl.gb new file mode 100644 index 0000000..50b3cc7 Binary files /dev/null and b/tests/cpu_instrs/individual/03-op sp,hl.gb differ diff --git a/tests/cpu_instrs/individual/04-op r,imm.gb b/tests/cpu_instrs/individual/04-op r,imm.gb new file mode 100644 index 0000000..58ca7b8 Binary files /dev/null and b/tests/cpu_instrs/individual/04-op r,imm.gb differ diff --git a/tests/cpu_instrs/individual/05-op rp.gb b/tests/cpu_instrs/individual/05-op rp.gb new file mode 100644 index 0000000..1c19d92 Binary files /dev/null and b/tests/cpu_instrs/individual/05-op rp.gb differ diff --git a/tests/cpu_instrs/individual/06-ld r,r.gb b/tests/cpu_instrs/individual/06-ld r,r.gb new file mode 100644 index 0000000..d497bfd Binary files /dev/null and b/tests/cpu_instrs/individual/06-ld r,r.gb differ diff --git a/tests/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb b/tests/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb new file mode 100644 index 0000000..5c8d20b Binary files /dev/null and b/tests/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb differ diff --git a/tests/cpu_instrs/individual/08-misc instrs.gb b/tests/cpu_instrs/individual/08-misc instrs.gb new file mode 100644 index 0000000..4da139b Binary files /dev/null and b/tests/cpu_instrs/individual/08-misc instrs.gb differ diff --git a/tests/cpu_instrs/individual/09-op r,r.gb b/tests/cpu_instrs/individual/09-op r,r.gb new file mode 100644 index 0000000..e30e6ec Binary files /dev/null and b/tests/cpu_instrs/individual/09-op r,r.gb differ diff --git a/tests/cpu_instrs/individual/10-bit ops.gb b/tests/cpu_instrs/individual/10-bit ops.gb new file mode 100644 index 0000000..8988458 Binary files /dev/null and b/tests/cpu_instrs/individual/10-bit ops.gb differ diff --git a/tests/cpu_instrs/individual/11-op a,(hl).gb b/tests/cpu_instrs/individual/11-op a,(hl).gb new file mode 100644 index 0000000..0634b7f Binary files /dev/null and b/tests/cpu_instrs/individual/11-op a,(hl).gb differ diff --git a/tests/cpu_instrs/readme.txt b/tests/cpu_instrs/readme.txt new file mode 100644 index 0000000..1aa7adb --- /dev/null +++ b/tests/cpu_instrs/readme.txt @@ -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