cpu/tests.rs: Modularize and add new tests
This commit is contained in:
parent
a2a44dfd4f
commit
185712aeb6
387
src/cpu/tests.rs
387
src/cpu/tests.rs
@ -1,5 +1,9 @@
|
|||||||
|
//! Tests for cpu.rs
|
||||||
|
//!
|
||||||
|
//! These run instructions, and ensure their output is consistent with previous builds
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
pub(self) use crate::{
|
||||||
bus,
|
bus,
|
||||||
bus::{Bus, Region::*},
|
bus::{Bus, Region::*},
|
||||||
};
|
};
|
||||||
@ -26,6 +30,8 @@ fn setup_environment() -> (CPU, Bus) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Unused instructions
|
/// Unused instructions
|
||||||
|
///
|
||||||
|
/// TODO: Exhaustively test unused instructions
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn unimplemented() {
|
fn unimplemented() {
|
||||||
@ -35,12 +41,14 @@ fn unimplemented() {
|
|||||||
cpu.unimplemented(0xffff);
|
cpu.unimplemented(0xffff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod sys {
|
||||||
|
use super::*;
|
||||||
/// 0aaa: Handles a "machine language function call" (lmao)
|
/// 0aaa: Handles a "machine language function call" (lmao)
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn sys() {
|
fn sys() {
|
||||||
let (mut cpu, mut bus) = setup_environment();
|
let (mut cpu, mut bus) = setup_environment();
|
||||||
bus.write(0x200u16, 0x0200u16); // 0x0200 is not one of the defined ML routines
|
bus.write(0x200u16, 0x0200u16); // 0x0200 is not one of the allowed routines
|
||||||
cpu.tick(&mut bus);
|
cpu.tick(&mut bus);
|
||||||
cpu.sys(0x200);
|
cpu.sys(0x200);
|
||||||
}
|
}
|
||||||
@ -73,17 +81,24 @@ fn ret() {
|
|||||||
assert!(dbg!(cpu.sp.wrapping_sub(sp_orig)) == 0x2);
|
assert!(dbg!(cpu.sp.wrapping_sub(sp_orig)) == 0x2);
|
||||||
// Verify the stack pointer has moved
|
// Verify the stack pointer has moved
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests control-flow instructions
|
||||||
|
///
|
||||||
|
/// Basically anything that touches the program counter
|
||||||
|
mod cf {
|
||||||
|
use super::*;
|
||||||
/// 1aaa: Sets the program counter to an absolute address
|
/// 1aaa: Sets the program counter to an absolute address
|
||||||
#[test]
|
#[test]
|
||||||
fn jump() {
|
fn jump() {
|
||||||
// Generate a random test address that's not 0x200
|
|
||||||
let test_addr = random::<u16>() & !0x200;
|
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
|
// Test all valid addresses
|
||||||
|
for addr in 0x000..0xffe {
|
||||||
// Call an address
|
// Call an address
|
||||||
cpu.jump(test_addr);
|
cpu.jump(addr);
|
||||||
// Verify the current address is the called address
|
// Verify the current address is the called address
|
||||||
assert_eq!(test_addr, cpu.pc);
|
assert_eq!(addr, cpu.pc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 2aaa: Pushes pc onto the stack, then jumps to a
|
/// 2aaa: Pushes pc onto the stack, then jumps to a
|
||||||
@ -104,7 +119,7 @@ fn call() {
|
|||||||
|
|
||||||
/// 3xbb: Skips the next instruction if register X == b
|
/// 3xbb: Skips the next instruction if register X == b
|
||||||
#[test]
|
#[test]
|
||||||
fn skip_if_x_equal_byte() {
|
fn skip_equals_immediate() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
for word in 0..=0xffff {
|
for word in 0..=0xffff {
|
||||||
let (a, b, addr) = (word as u8, (word >> 4) as u8, random::<u16>() & 0x7fe);
|
let (a, b, addr) = (word as u8, (word >> 4) as u8, random::<u16>() & 0x7fe);
|
||||||
@ -114,7 +129,7 @@ fn skip_if_x_equal_byte() {
|
|||||||
// set the register under test to a
|
// set the register under test to a
|
||||||
cpu.v[x] = a;
|
cpu.v[x] = a;
|
||||||
// do the thing
|
// do the thing
|
||||||
cpu.skip_if_x_equal_byte(x, b);
|
cpu.skip_equals_immediate(x, b);
|
||||||
// validate the result
|
// validate the result
|
||||||
assert_eq!(cpu.pc, addr.wrapping_add(if dbg!(a == b) { 2 } else { 0 }));
|
assert_eq!(cpu.pc, addr.wrapping_add(if dbg!(a == b) { 2 } else { 0 }));
|
||||||
}
|
}
|
||||||
@ -123,7 +138,7 @@ fn skip_if_x_equal_byte() {
|
|||||||
|
|
||||||
/// 4xbb: Skips the next instruction if register X != b
|
/// 4xbb: Skips the next instruction if register X != b
|
||||||
#[test]
|
#[test]
|
||||||
fn skip_if_x_not_equal_byte() {
|
fn skip_not_equals_immediate() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
for word in 0..=0xffff {
|
for word in 0..=0xffff {
|
||||||
let (a, b, addr) = (word as u8, (word >> 4) as u8, random::<u16>() & 0x7fe);
|
let (a, b, addr) = (word as u8, (word >> 4) as u8, random::<u16>() & 0x7fe);
|
||||||
@ -133,7 +148,7 @@ fn skip_if_x_not_equal_byte() {
|
|||||||
// set the register under test to a
|
// set the register under test to a
|
||||||
cpu.v[x] = a;
|
cpu.v[x] = a;
|
||||||
// do the thing
|
// do the thing
|
||||||
cpu.skip_if_x_not_equal_byte(x, b);
|
cpu.skip_not_equals_immediate(x, b);
|
||||||
// validate the result
|
// validate the result
|
||||||
assert_eq!(cpu.pc, addr.wrapping_add(if a != b { 2 } else { 0 }));
|
assert_eq!(cpu.pc, addr.wrapping_add(if a != b { 2 } else { 0 }));
|
||||||
}
|
}
|
||||||
@ -142,7 +157,7 @@ fn skip_if_x_not_equal_byte() {
|
|||||||
|
|
||||||
/// 5xy0: Skips the next instruction if register X != register Y
|
/// 5xy0: Skips the next instruction if register X != register Y
|
||||||
#[test]
|
#[test]
|
||||||
fn skip_if_x_equal_y() {
|
fn skip_equals() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
for word in 0..=0xffff {
|
for word in 0..=0xffff {
|
||||||
let (a, b, addr) = (word as u8, (word >> 4) as u8, random::<u16>() & 0x7fe);
|
let (a, b, addr) = (word as u8, (word >> 4) as u8, random::<u16>() & 0x7fe);
|
||||||
@ -156,12 +171,57 @@ fn skip_if_x_equal_y() {
|
|||||||
// set the registers under test to a, b
|
// set the registers under test to a, b
|
||||||
(cpu.v[x], cpu.v[y]) = (a, b);
|
(cpu.v[x], cpu.v[y]) = (a, b);
|
||||||
// do the thing
|
// do the thing
|
||||||
cpu.skip_if_x_equal_y(x, y);
|
cpu.skip_equals(x, y);
|
||||||
// validate the result
|
// validate the result
|
||||||
assert_eq!(cpu.pc, addr.wrapping_add(if a == b { 2 } else { 0 }));
|
assert_eq!(cpu.pc, addr.wrapping_add(if a == b { 2 } else { 0 }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 9xy0: Skip next instruction if X != y
|
||||||
|
#[test]
|
||||||
|
fn skip_not_equals() {
|
||||||
|
let (mut cpu, _) = setup_environment();
|
||||||
|
for word in 0..=0xffff {
|
||||||
|
let (a, b, addr) = (word as u8, (word >> 4) as u8, random::<u16>() & 0x7fe);
|
||||||
|
for reg in 0..=0xff {
|
||||||
|
let (x, y) = (reg & 0xf, reg >> 4);
|
||||||
|
if x == y {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// set the PC to a random address
|
||||||
|
cpu.pc = addr;
|
||||||
|
// set the registers under test to a, b
|
||||||
|
(cpu.v[x], cpu.v[y]) = (a, b);
|
||||||
|
// do the thing
|
||||||
|
cpu.skip_not_equals(x, y);
|
||||||
|
// validate the result
|
||||||
|
assert_eq!(cpu.pc, addr.wrapping_add(if a != b { 2 } else { 0 }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Badr: Jump to &adr + v0
|
||||||
|
#[test]
|
||||||
|
fn jump_indexed() {
|
||||||
|
let (mut cpu, _) = setup_environment();
|
||||||
|
// For every valid address
|
||||||
|
for addr in 0..0x1000 {
|
||||||
|
// For every valid offset
|
||||||
|
for v0 in 0..=0xff {
|
||||||
|
// set v[0] = v0
|
||||||
|
cpu.v[0] = v0;
|
||||||
|
// jump indexed
|
||||||
|
cpu.jump_indexed(addr);
|
||||||
|
// Validate register set
|
||||||
|
assert_eq!(cpu.pc, addr.wrapping_add(v0.into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod math {
|
||||||
|
use super::*;
|
||||||
/// 6xbb: Loads immediate byte b into register vX
|
/// 6xbb: Loads immediate byte b into register vX
|
||||||
#[test]
|
#[test]
|
||||||
fn load_immediate() {
|
fn load_immediate() {
|
||||||
@ -192,8 +252,9 @@ fn add_immediate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 8xy0: Loads the value of y into x
|
/// 8xy0: Loads the value of y into x
|
||||||
|
// TODO: Test with authentic flag set
|
||||||
#[test]
|
#[test]
|
||||||
fn load_y_into_x() {
|
fn load() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
// We use zero as a sentinel value for this test, so loop from 1 to 255
|
// We use zero as a sentinel value for this test, so loop from 1 to 255
|
||||||
for test_value in 1..=0xff {
|
for test_value in 1..=0xff {
|
||||||
@ -206,7 +267,7 @@ fn load_y_into_x() {
|
|||||||
cpu.v[y] = test_value;
|
cpu.v[y] = test_value;
|
||||||
// zero X
|
// zero X
|
||||||
cpu.v[x] = 0;
|
cpu.v[x] = 0;
|
||||||
cpu.load_y_into_x(x, y);
|
cpu.load(x, y);
|
||||||
// verify results
|
// verify results
|
||||||
assert_eq!(cpu.v[x], test_value);
|
assert_eq!(cpu.v[x], test_value);
|
||||||
assert_eq!(cpu.v[y], test_value);
|
assert_eq!(cpu.v[y], test_value);
|
||||||
@ -215,8 +276,9 @@ fn load_y_into_x() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 8xy1: Performs bitwise or of vX and vY, and stores the result in vX
|
/// 8xy1: Performs bitwise or of vX and vY, and stores the result in vX
|
||||||
|
// TODO: Test with authentic flag set
|
||||||
#[test]
|
#[test]
|
||||||
fn x_orequals_y() {
|
fn or() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
for word in 0..=0xffff {
|
for word in 0..=0xffff {
|
||||||
let (a, b) = (word as u8, (word >> 4) as u8);
|
let (a, b) = (word as u8, (word >> 4) as u8);
|
||||||
@ -227,7 +289,7 @@ fn x_orequals_y() {
|
|||||||
(cpu.v[x], cpu.v[y]) = (a, b);
|
(cpu.v[x], cpu.v[y]) = (a, b);
|
||||||
|
|
||||||
// do the thing
|
// do the thing
|
||||||
cpu.x_orequals_y(x, y);
|
cpu.or(x, y);
|
||||||
|
|
||||||
// validate the result
|
// validate the result
|
||||||
assert_eq!(cpu.v[x], if x == y { b } else { expected_result });
|
assert_eq!(cpu.v[x], if x == y { b } else { expected_result });
|
||||||
@ -236,8 +298,9 @@ fn x_orequals_y() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 8xy2: Performs bitwise and of vX and vY, and stores the result in vX
|
/// 8xy2: Performs bitwise and of vX and vY, and stores the result in vX
|
||||||
|
// TODO: Test with authentic flag set
|
||||||
#[test]
|
#[test]
|
||||||
fn x_andequals_y() {
|
fn and() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
for word in 0..=0xffff {
|
for word in 0..=0xffff {
|
||||||
let (a, b) = (word as u8, (word >> 4) as u8);
|
let (a, b) = (word as u8, (word >> 4) as u8);
|
||||||
@ -248,7 +311,7 @@ fn x_andequals_y() {
|
|||||||
(cpu.v[x], cpu.v[y]) = (a, b);
|
(cpu.v[x], cpu.v[y]) = (a, b);
|
||||||
|
|
||||||
// do the thing
|
// do the thing
|
||||||
cpu.x_andequals_y(x, y);
|
cpu.and(x, y);
|
||||||
|
|
||||||
// validate the result
|
// validate the result
|
||||||
assert_eq!(cpu.v[x], if x == y { b } else { expected_result });
|
assert_eq!(cpu.v[x], if x == y { b } else { expected_result });
|
||||||
@ -257,8 +320,9 @@ fn x_andequals_y() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 8xy3: Performs bitwise xor of vX and vY, and stores the result in vX
|
/// 8xy3: Performs bitwise xor of vX and vY, and stores the result in vX
|
||||||
|
// TODO: Test with authentic flag set
|
||||||
#[test]
|
#[test]
|
||||||
fn x_xorequals_y() {
|
fn xor() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
for word in 0..=0xffff {
|
for word in 0..=0xffff {
|
||||||
let (a, b) = (word as u8, (word >> 4) as u8);
|
let (a, b) = (word as u8, (word >> 4) as u8);
|
||||||
@ -269,7 +333,7 @@ fn x_xorequals_y() {
|
|||||||
(cpu.v[x], cpu.v[y]) = (a, b);
|
(cpu.v[x], cpu.v[y]) = (a, b);
|
||||||
|
|
||||||
// do the thing
|
// do the thing
|
||||||
cpu.x_xorequals_y(x, y);
|
cpu.xor(x, y);
|
||||||
|
|
||||||
// validate the result
|
// validate the result
|
||||||
assert_eq!(cpu.v[x], if x == y { 0 } else { expected_result });
|
assert_eq!(cpu.v[x], if x == y { 0 } else { expected_result });
|
||||||
@ -280,7 +344,7 @@ fn x_xorequals_y() {
|
|||||||
/// 8xy4: Performs addition of vX and vY, and stores the result in vX, carry in vF
|
/// 8xy4: Performs addition of vX and vY, and stores the result in vX, carry in vF
|
||||||
/// If X is F, *only* stores borrow
|
/// If X is F, *only* stores borrow
|
||||||
#[test]
|
#[test]
|
||||||
fn x_addequals_y() {
|
fn add() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
for word in 0..=0xffff {
|
for word in 0..=0xffff {
|
||||||
let (a, b) = (word as u8, (word >> 4) as u8);
|
let (a, b) = (word as u8, (word >> 4) as u8);
|
||||||
@ -293,7 +357,7 @@ fn x_addequals_y() {
|
|||||||
(cpu.v[x], cpu.v[y]) = (a, b);
|
(cpu.v[x], cpu.v[y]) = (a, b);
|
||||||
|
|
||||||
// do the thing
|
// do the thing
|
||||||
cpu.x_addequals_y(x, y);
|
cpu.add(x, y);
|
||||||
|
|
||||||
// validate the result
|
// validate the result
|
||||||
// if the destination is vF, the result was discarded, and only the carry was kept
|
// if the destination is vF, the result was discarded, and only the carry was kept
|
||||||
@ -307,7 +371,7 @@ fn x_addequals_y() {
|
|||||||
|
|
||||||
/// 8xy5: Performs subtraction of vX and vY, and stores the result in vX, borrow in vF
|
/// 8xy5: Performs subtraction of vX and vY, and stores the result in vX, borrow in vF
|
||||||
#[test]
|
#[test]
|
||||||
fn x_subequals_y() {
|
fn sub() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
for word in 0..=0xffff {
|
for word in 0..=0xffff {
|
||||||
let (a, b) = (word as u8, (word >> 4) as u8);
|
let (a, b) = (word as u8, (word >> 4) as u8);
|
||||||
@ -319,7 +383,7 @@ fn x_subequals_y() {
|
|||||||
(cpu.v[x], cpu.v[y]) = (a, b);
|
(cpu.v[x], cpu.v[y]) = (a, b);
|
||||||
|
|
||||||
// do the thing
|
// do the thing
|
||||||
cpu.x_subequals_y(x, y);
|
cpu.sub(x, y);
|
||||||
|
|
||||||
// validate the result
|
// validate the result
|
||||||
// if the destination is vF, the result was discarded, and only the carry was kept
|
// if the destination is vF, the result was discarded, and only the carry was kept
|
||||||
@ -333,22 +397,21 @@ fn x_subequals_y() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 8xy6: Performs bitwise right shift of vX, stores carry-out in vF
|
/// 8xy6: Performs bitwise right shift of vX, stores carry-out in vF
|
||||||
|
// TODO: Test with authentic flag set
|
||||||
#[test]
|
#[test]
|
||||||
fn shift_right_x() {
|
fn shift_right() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
for word in 0..=0xff {
|
for word in 0..=0xff {
|
||||||
for x in 0..=0xf {
|
for x in 0..=0xf {
|
||||||
// set the register under test to `word`
|
// set the register under test to `word`
|
||||||
cpu.v[x] = word;
|
cpu.v[x] = word;
|
||||||
// calculate the expected result
|
|
||||||
let expected = word >> 1;
|
|
||||||
// do the thing
|
// do the thing
|
||||||
cpu.shift_right_x(x);
|
cpu.shift_right(x, x);
|
||||||
|
|
||||||
// validate the result
|
// validate the result
|
||||||
// if the destination is vF, the result was discarded, and only the carry was kept
|
// if the destination is vF, the result was discarded, and only the carry was kept
|
||||||
if x != 0xf {
|
if x != 0xf {
|
||||||
assert_eq!(cpu.v[x], expected);
|
assert_eq!(cpu.v[x], word >> 1);
|
||||||
}
|
}
|
||||||
// The borrow flag for subtraction is inverted
|
// The borrow flag for subtraction is inverted
|
||||||
assert_eq!(cpu.v[0xf], word & 1);
|
assert_eq!(cpu.v[0xf], word & 1);
|
||||||
@ -358,7 +421,7 @@ fn shift_right_x() {
|
|||||||
|
|
||||||
/// 8xy7: Performs subtraction of vY and vX, and stores the result in vX and ~carry in vF
|
/// 8xy7: Performs subtraction of vY and vX, and stores the result in vX and ~carry in vF
|
||||||
#[test]
|
#[test]
|
||||||
fn backwards_subtract() {
|
fn backwards_sub() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
for word in 0..=0xffff {
|
for word in 0..=0xffff {
|
||||||
let (a, b) = (word as u8, (word >> 4) as u8);
|
let (a, b) = (word as u8, (word >> 4) as u8);
|
||||||
@ -370,7 +433,7 @@ fn backwards_subtract() {
|
|||||||
(cpu.v[x], cpu.v[y]) = (a, b);
|
(cpu.v[x], cpu.v[y]) = (a, b);
|
||||||
|
|
||||||
// do the thing
|
// do the thing
|
||||||
cpu.backwards_subtract(x, y);
|
cpu.backwards_sub(x, y);
|
||||||
|
|
||||||
// validate the result
|
// validate the result
|
||||||
// if the destination is vF, the result was discarded, and only the carry was kept
|
// if the destination is vF, the result was discarded, and only the carry was kept
|
||||||
@ -384,83 +447,72 @@ fn backwards_subtract() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 8X_E: Performs bitwise left shift of vX
|
/// 8X_E: Performs bitwise left shift of vX
|
||||||
|
// TODO: Test with authentic flag set
|
||||||
#[test]
|
#[test]
|
||||||
fn shift_left_x() {
|
fn shift_left() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
for word in 0..=0xff {
|
for word in 0..=0xff {
|
||||||
for x in 0..=0xf {
|
for x in 0..=0xf {
|
||||||
// set the register under test to `word`
|
// set the register under test to `word`
|
||||||
cpu.v[x] = word;
|
cpu.v[x] = word;
|
||||||
// calculate the expected result
|
|
||||||
let expected = word << 1;
|
|
||||||
// do the thing
|
// do the thing
|
||||||
cpu.shift_left_x(x);
|
cpu.shift_left(x, x);
|
||||||
|
|
||||||
// validate the result
|
// validate the result
|
||||||
// if the destination is vF, the result was discarded, and only the carry was kept
|
// if the destination is vF, the result was discarded, and only the carry was kept
|
||||||
if x != 0xf {
|
if x != 0xf {
|
||||||
assert_eq!(cpu.v[x], expected);
|
assert_eq!(cpu.v[x], word << 1);
|
||||||
}
|
}
|
||||||
// The borrow flag for subtraction is inverted
|
// The borrow flag for subtraction is inverted
|
||||||
assert_eq!(cpu.v[0xf], word >> 7);
|
assert_eq!(cpu.v[0xf], word >> 7);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 9xy0: Skip next instruction if X != y
|
|
||||||
#[test]
|
|
||||||
fn skip_if_x_not_equal_y() {
|
|
||||||
let (mut cpu, _) = setup_environment();
|
|
||||||
for word in 0..=0xffff {
|
|
||||||
let (a, b, addr) = (word as u8, (word >> 4) as u8, random::<u16>() & 0x7fe);
|
|
||||||
for reg in 0..=0xff {
|
|
||||||
let (x, y) = (reg & 0xf, reg >> 4);
|
|
||||||
if x == y {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// set the PC to a random address
|
|
||||||
cpu.pc = addr;
|
|
||||||
// set the registers under test to a, b
|
|
||||||
(cpu.v[x], cpu.v[y]) = (a, b);
|
|
||||||
// do the thing
|
|
||||||
cpu.skip_if_x_not_equal_y(x, y);
|
|
||||||
// validate the result
|
|
||||||
assert_eq!(cpu.pc, addr.wrapping_add(if a != b { 2 } else { 0 }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test operations on the index/indirect register, I
|
||||||
|
mod i {
|
||||||
|
use super::*;
|
||||||
/// Aadr: Load address #adr into register I
|
/// Aadr: Load address #adr into register I
|
||||||
#[test]
|
#[test]
|
||||||
fn load_indirect_register() {
|
fn load_i_immediate() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
// For every valid address
|
|
||||||
for addr in 0..0x1000 {
|
for addr in 0..0x1000 {
|
||||||
// Load indirect register
|
// Load indirect register
|
||||||
cpu.load_indirect_register(addr);
|
cpu.load_i_immediate(addr);
|
||||||
// Validate register set
|
// Validate register set to addr
|
||||||
assert_eq!(cpu.i, addr);
|
assert_eq!(cpu.i, addr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Badr: Jump to &adr + v0
|
/// Fx1e: Add vX to I
|
||||||
#[test]
|
#[test]
|
||||||
fn jump_indexed() {
|
fn add_i() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
// For every valid address
|
// For every valid address
|
||||||
for addr in 0..0x1000 {
|
for addr in 0..0x1000 {
|
||||||
// For every valid offset
|
// For every valid offset
|
||||||
for v0 in 0..=0xff {
|
for x in 0..=0xfff {
|
||||||
// set v[0] = v0
|
let (x, byte) = (x >> 8, x as u8);
|
||||||
cpu.v[0] = v0;
|
// set v[x] = byte
|
||||||
// jump indexed
|
(cpu.i, cpu.v[x]) = (addr as u16, byte);
|
||||||
cpu.jump_indexed(addr);
|
// add vX to indirect register
|
||||||
|
cpu.add_i(x);
|
||||||
// Validate register set
|
// Validate register set
|
||||||
assert_eq!(cpu.pc, addr.wrapping_add(v0.into()));
|
assert_eq!(cpu.i, (addr + byte as usize) as u16)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Screen, buttons, other things that would be peripherals on a real architecture
|
||||||
|
/// # Includes:
|
||||||
|
/// - Random number generation
|
||||||
|
/// - Drawing to the display
|
||||||
|
mod io {
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
/// Cxbb: Stores a random number & the provided byte into vX
|
/// Cxbb: Stores a random number & the provided byte into vX
|
||||||
//#[test]
|
//#[test]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -468,6 +520,8 @@ fn rand() {
|
|||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod display {
|
||||||
|
use super::*;
|
||||||
struct ScreenTest {
|
struct ScreenTest {
|
||||||
program: &'static [u8],
|
program: &'static [u8],
|
||||||
screen: &'static [u8],
|
screen: &'static [u8],
|
||||||
@ -519,51 +573,49 @@ fn draw() {
|
|||||||
assert_eq!(bus.get_region(Screen).unwrap(), test.screen);
|
assert_eq!(bus.get_region(Screen).unwrap(), test.screen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod cf {
|
||||||
|
//use super::*;
|
||||||
/// Ex9E: Skip next instruction if key == #X
|
/// Ex9E: Skip next instruction if key == #X
|
||||||
//#[test]
|
//#[test]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn skip_if_key_equals_x() {
|
fn skip_key_equals() {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ExaE: Skip next instruction if key != #X
|
/// ExaE: Skip next instruction if key != #X
|
||||||
//#[test]
|
//#[test]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn skip_if_key_not_x() {
|
fn skip_key_not_equals() {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fx07: Get the current DT, and put it in vX
|
|
||||||
/// ```py
|
|
||||||
/// vX = DT
|
|
||||||
/// ```
|
|
||||||
#[test]
|
|
||||||
fn get_delay_timer() {
|
|
||||||
let (mut cpu, _) = setup_environment();
|
|
||||||
for word in 0..=0xff {
|
|
||||||
for x in 0..=0xf {
|
|
||||||
// set the register under test to `word`
|
|
||||||
cpu.delay = word;
|
|
||||||
// do the thing
|
|
||||||
cpu.get_delay_timer(x);
|
|
||||||
// validate the result
|
|
||||||
assert_eq!(cpu.v[x], word);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fx0A: Wait for key, then vX = K
|
/// Fx0A: Wait for key, then vX = K
|
||||||
//#[test]
|
//#[test]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn wait_for_key() {
|
fn wait_for_key() {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fx07: Get the current DT, and put it in vX
|
||||||
|
#[test]
|
||||||
|
fn get_delay_timer() {
|
||||||
|
let (mut cpu, _) = setup_environment();
|
||||||
|
for word in 0..=0xff {
|
||||||
|
for x in 0..=0xf {
|
||||||
|
// set the register under test to `word`
|
||||||
|
cpu.delay = word;
|
||||||
|
// do the thing
|
||||||
|
cpu.load_delay_timer(x);
|
||||||
|
// validate the result
|
||||||
|
assert_eq!(cpu.v[x], word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Fx15: Load vX into DT
|
/// Fx15: Load vX into DT
|
||||||
/// ```py
|
|
||||||
/// DT = vX
|
|
||||||
/// ```
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load_delay_timer() {
|
fn load_delay_timer() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
@ -572,7 +624,7 @@ fn load_delay_timer() {
|
|||||||
// set the register under test to `word`
|
// set the register under test to `word`
|
||||||
cpu.v[x] = word;
|
cpu.v[x] = word;
|
||||||
// do the thing
|
// do the thing
|
||||||
cpu.load_delay_timer(x);
|
cpu.store_delay_timer(x);
|
||||||
// validate the result
|
// validate the result
|
||||||
assert_eq!(cpu.delay, word);
|
assert_eq!(cpu.delay, word);
|
||||||
}
|
}
|
||||||
@ -580,9 +632,6 @@ fn load_delay_timer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Fx18: Load vX into ST
|
/// Fx18: Load vX into ST
|
||||||
/// ```py
|
|
||||||
/// ST = vX;
|
|
||||||
/// ```
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load_sound_timer() {
|
fn load_sound_timer() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
@ -591,53 +640,113 @@ fn load_sound_timer() {
|
|||||||
// set the register under test to `word`
|
// set the register under test to `word`
|
||||||
cpu.v[x] = word;
|
cpu.v[x] = word;
|
||||||
// do the thing
|
// do the thing
|
||||||
cpu.load_sound_timer(x);
|
cpu.store_sound_timer(x);
|
||||||
// validate the result
|
// validate the result
|
||||||
assert_eq!(cpu.sound, word);
|
assert_eq!(cpu.sound, word);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fx1e: Add vX to I,
|
mod sprite {
|
||||||
/// ```py
|
use super::*;
|
||||||
/// I += vX;
|
|
||||||
/// ```
|
struct SpriteTest {
|
||||||
#[test]
|
input: u8,
|
||||||
fn add_to_indirect() {
|
output: &'static [u8],
|
||||||
let (mut cpu, _) = setup_environment();
|
|
||||||
// For every valid address
|
|
||||||
for addr in 0..0x1000 {
|
|
||||||
// For every valid offset
|
|
||||||
for x in 0..=0xfff {
|
|
||||||
let (x, byte) = (x >> 8, x as u8);
|
|
||||||
// set v[x] = byte
|
|
||||||
(cpu.i, cpu.v[x]) = (addr as u16, byte);
|
|
||||||
// add vX to indirect register
|
|
||||||
cpu.add_to_indirect(x);
|
|
||||||
// Validate register set
|
|
||||||
assert_eq!(cpu.i, (addr + byte as usize) as u16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const TESTS: [SpriteTest; 16] = [
|
||||||
|
SpriteTest { input: 0x0, output: &[0xf0, 0x90, 0x90, 0x90, 0xf0] },
|
||||||
|
SpriteTest { input: 0x1, output: &[0x20, 0x60, 0x20, 0x20, 0x70] },
|
||||||
|
SpriteTest { input: 0x2, output: &[0xf0, 0x10, 0xf0, 0x80, 0xf0] },
|
||||||
|
SpriteTest { input: 0x3, output: &[0xf0, 0x10, 0xf0, 0x10, 0xf0] },
|
||||||
|
SpriteTest { input: 0x4, output: &[0x90, 0x90, 0xf0, 0x10, 0x10] },
|
||||||
|
SpriteTest { input: 0x5, output: &[0xf0, 0x80, 0xf0, 0x10, 0xf0] },
|
||||||
|
SpriteTest { input: 0x6, output: &[0xf0, 0x80, 0xf0, 0x90, 0xf0] },
|
||||||
|
SpriteTest { input: 0x7, output: &[0xf0, 0x10, 0x20, 0x40, 0x40] },
|
||||||
|
SpriteTest { input: 0x8, output: &[0xf0, 0x90, 0xf0, 0x90, 0xf0] },
|
||||||
|
SpriteTest { input: 0x9, output: &[0xf0, 0x90, 0xf0, 0x10, 0xf0] },
|
||||||
|
SpriteTest { input: 0xa, output: &[0xf0, 0x90, 0xf0, 0x90, 0x90] },
|
||||||
|
SpriteTest { input: 0xb, output: &[0xe0, 0x90, 0xe0, 0x90, 0xe0] },
|
||||||
|
SpriteTest { input: 0xc, output: &[0xf0, 0x80, 0x80, 0x80, 0xf0] },
|
||||||
|
SpriteTest { input: 0xd, output: &[0xe0, 0x90, 0x90, 0x90, 0xe0] },
|
||||||
|
SpriteTest { input: 0xe, output: &[0xf0, 0x80, 0xf0, 0x80, 0xf0] },
|
||||||
|
SpriteTest { input: 0xf, output: &[0xf0, 0x80, 0xf0, 0x80, 0x80] },
|
||||||
|
];
|
||||||
|
|
||||||
/// Fx29: Load sprite for character vX into I
|
/// Fx29: Load sprite for character vX into I
|
||||||
/// ```py
|
#[test]
|
||||||
/// I = sprite(vX);
|
|
||||||
/// ```
|
|
||||||
//#[test]
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn load_sprite_x() {
|
fn load_sprite() {
|
||||||
todo!()
|
let (mut cpu, bus) = setup_environment();
|
||||||
|
for test in TESTS {
|
||||||
|
let reg = 0xf & random::<usize>();
|
||||||
|
// load number into CPU register
|
||||||
|
cpu.v[reg] = test.input;
|
||||||
|
|
||||||
|
cpu.load_sprite(reg);
|
||||||
|
|
||||||
|
let addr = cpu.i as usize;
|
||||||
|
assert_eq!(
|
||||||
|
bus.get(addr..addr.wrapping_add(5)).unwrap(),
|
||||||
|
test.output,
|
||||||
|
"\nTesting load_sprite({:x}) -> {:04x} {{\n\tout:{:02x?}\n\texp:{:02x?}\n}}",
|
||||||
|
test.input,
|
||||||
|
cpu.i,
|
||||||
|
bus.get(addr..addr.wrapping_add(5)).unwrap(),
|
||||||
|
test.output,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod bcdtest {
|
||||||
|
pub(self) use super::*;
|
||||||
|
|
||||||
|
struct BCDTest {
|
||||||
|
// value to test
|
||||||
|
input: u8,
|
||||||
|
// result
|
||||||
|
output: &'static [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
const BCD_TESTS: [BCDTest; 3] = [
|
||||||
|
BCDTest {
|
||||||
|
input: 000,
|
||||||
|
output: &[0, 0, 0],
|
||||||
|
},
|
||||||
|
BCDTest {
|
||||||
|
input: 255,
|
||||||
|
output: &[2, 5, 5],
|
||||||
|
},
|
||||||
|
BCDTest {
|
||||||
|
input: 127,
|
||||||
|
output: &[1, 2, 7],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
/// Fx33: BCD convert X into I`[0..3]`
|
/// Fx33: BCD convert X into I`[0..3]`
|
||||||
//#[test]
|
#[test]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn bcd_convert_i() {
|
fn bcd_convert() {
|
||||||
todo!()
|
for test in BCD_TESTS {
|
||||||
|
let (mut cpu, mut bus) = setup_environment();
|
||||||
|
let addr = 0xff0 & random::<u16>() as usize;
|
||||||
|
// load CPU registers
|
||||||
|
cpu.i = addr as u16;
|
||||||
|
cpu.v[5] = test.input;
|
||||||
|
// run instruction
|
||||||
|
cpu.bcd_convert(5, &mut bus);
|
||||||
|
// validate the results
|
||||||
|
assert_eq!(bus.get(addr..addr.saturating_add(3)), Some(test.output))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fx55: DMA Stor from I to registers 0..X
|
/// Fx55: DMA Stor from I to registers 0..X
|
||||||
|
// TODO: Test with authentic flag unset
|
||||||
|
// TODO: Test with authentic flag set
|
||||||
//#[test]
|
//#[test]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn dma_store() {
|
fn dma_store() {
|
||||||
@ -648,10 +757,28 @@ fn dma_store() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Fx65: DMA Load from I to registers 0..X
|
/// Fx65: DMA Load from I to registers 0..X
|
||||||
//#[test]
|
// TODO: Test with authentic flag unset
|
||||||
|
// TODO: Test with authentic flag set
|
||||||
|
#[test]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn dma_load() {
|
fn dma_load() {
|
||||||
todo!()
|
let (mut cpu, mut bus) = setup_environment();
|
||||||
|
const DATA: &[u8] = b"ABCDEFGHIJKLMNOP";
|
||||||
|
// Load some test data into memory
|
||||||
|
let addr = 0x456;
|
||||||
|
bus.get_mut(addr..addr + DATA.len())
|
||||||
|
.unwrap()
|
||||||
|
.write(DATA)
|
||||||
|
.unwrap();
|
||||||
|
for len in 0..16 {
|
||||||
// Perform DMA load
|
// Perform DMA load
|
||||||
|
cpu.i = addr as u16;
|
||||||
|
cpu.load_dma(len, &mut bus);
|
||||||
// Check that registers grabbed the correct data
|
// Check that registers grabbed the correct data
|
||||||
|
assert_eq!(cpu.v[0..=len], DATA[0..=len]);
|
||||||
|
assert_eq!(cpu.v[len + 1..], [0; 16][len + 1..]);
|
||||||
|
// clear
|
||||||
|
cpu.v = [0; 16];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user