Fix HL+, add cpu instr testsuite
This commit is contained in:
parent
e3fce0bcc5
commit
d91fa7d75b
@ -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.")
|
||||
}
|
||||
|
||||
228
src/cpu.rs
228
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,18 +491,17 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -472,7 +521,7 @@ impl CPU {
|
||||
|
||||
self.ip = self.ip.wrapping_add(1);
|
||||
|
||||
let cycles: u8 = match instruction {
|
||||
cycles = match instruction {
|
||||
0x00 => {
|
||||
if self.debug {
|
||||
println!("NOP");
|
||||
@ -481,6 +530,7 @@ impl CPU {
|
||||
},
|
||||
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),
|
||||
@ -551,6 +601,28 @@ impl CPU {
|
||||
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 {
|
||||
@ -575,7 +647,7 @@ impl CPU {
|
||||
}
|
||||
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);
|
||||
self.set_pair_value(REG_H, REG_L, addr.wrapping_add(1));
|
||||
8
|
||||
},
|
||||
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);
|
||||
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),
|
||||
@ -656,6 +729,7 @@ impl CPU {
|
||||
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 {
|
||||
@ -719,6 +793,15 @@ impl CPU {
|
||||
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;
|
||||
@ -838,18 +921,11 @@ impl CPU {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
self.set_clear_flag(FLAG_C, rval < val);
|
||||
self.set_clear_flag(FLAG_Z, rval == val);
|
||||
4
|
||||
},
|
||||
0xC0 => {
|
||||
@ -950,7 +1026,36 @@ impl CPU {
|
||||
self.call(target);
|
||||
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),
|
||||
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 => {
|
||||
@ -958,10 +1063,45 @@ impl CPU {
|
||||
if self.debug {
|
||||
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
|
||||
},
|
||||
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);
|
||||
@ -1006,6 +1146,22 @@ impl CPU {
|
||||
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);
|
||||
@ -1059,20 +1215,13 @@ impl CPU {
|
||||
},
|
||||
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);
|
||||
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);
|
||||
}
|
||||
self.set_clear_flag(FLAG_C, args[0] > rval);
|
||||
self.set_clear_flag(FLAG_Z, args[0] == rval);
|
||||
|
||||
// TODO H
|
||||
8
|
||||
@ -1080,6 +1229,7 @@ impl CPU {
|
||||
0xFF => self.rst(0x38),
|
||||
_ => panic!("Unknown instruction: {:02x}", instruction)
|
||||
};
|
||||
}
|
||||
// self.dump_stack();
|
||||
self.interconnect.tick(cycles);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
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