updates tests to cover most code.
-> Draw Sprite -> Ret -> Call
This commit is contained in:
parent
d1e39307a7
commit
8c3cc5085c
@ -1,5 +1,5 @@
|
|||||||
[alias]
|
[alias]
|
||||||
coverage = "tarpaulin --out Html --skip-clean"
|
coverage = "tarpaulin --out Html --skip-clean --output-dir coverage"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
# rustc-wrapper = "sccache"
|
rustc-wrapper = "sccache"
|
||||||
|
|||||||
0
coverage.sh
Normal file
0
coverage.sh
Normal file
@ -22,7 +22,6 @@ pub struct Chip8Computer {
|
|||||||
pub state: Chip8CpuStates,
|
pub state: Chip8CpuStates,
|
||||||
pub keypad: Keypad
|
pub keypad: Keypad
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Chip8Computer {
|
impl Default for Chip8Computer {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -77,8 +76,17 @@ impl Chip8Computer {
|
|||||||
// todo: THIS IS BAD AND IS A SIDE EFFECT
|
// todo: THIS IS BAD AND IS A SIDE EFFECT
|
||||||
decoded_instruction.execute(self);
|
decoded_instruction.execute(self);
|
||||||
|
|
||||||
self.sound_timer.tick();
|
match self.state {
|
||||||
self.delay_timer.tick();
|
Chip8CpuStates::WaitingForInstruction => {
|
||||||
|
self.sound_timer.tick();
|
||||||
|
self.delay_timer.tick();
|
||||||
|
}
|
||||||
|
Chip8CpuStates::WaitingForKey => {
|
||||||
|
println!("waiting for a key press...");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
pub enum Chip8CpuStates {
|
pub enum Chip8CpuStates {
|
||||||
#[default]
|
#[default]
|
||||||
WaitingForInstruction,
|
WaitingForInstruction,
|
||||||
|
WaitingForKey,
|
||||||
ExecutingInstruction,
|
ExecutingInstruction,
|
||||||
Error,
|
Error,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ use imgui::ColorPicker3;
|
|||||||
use log::debug;
|
use log::debug;
|
||||||
use rand::random;
|
use rand::random;
|
||||||
use crate::chip8::computer::{Chip8Computer};
|
use crate::chip8::computer::{Chip8Computer};
|
||||||
|
use crate::chip8::cpu_states::Chip8CpuStates::WaitingForKey;
|
||||||
use crate::chip8::instructions::Chip8CpuInstructions::XXXXERRORINSTRUCTION;
|
use crate::chip8::instructions::Chip8CpuInstructions::XXXXERRORINSTRUCTION;
|
||||||
use crate::chip8::util::InstructionUtil;
|
use crate::chip8::util::InstructionUtil;
|
||||||
use crate::chip8::video::Chip8Video;
|
use crate::chip8::video::Chip8Video;
|
||||||
@ -424,7 +425,7 @@ impl Chip8CpuInstructions {
|
|||||||
// 0x0nnn Exit to System Call
|
// 0x0nnn Exit to System Call
|
||||||
Chip8CpuInstructions::SysAddr(new_address) => {
|
Chip8CpuInstructions::SysAddr(new_address) => {
|
||||||
debug!("SysAddr [0x{new_address:3x}]");
|
debug!("SysAddr [0x{new_address:3x}]");
|
||||||
// println!("SYS TO [{new_address}]");
|
|
||||||
input.registers.poke_pc(*new_address as u16);
|
input.registers.poke_pc(*new_address as u16);
|
||||||
}
|
}
|
||||||
// * 0x00E0 Clear Screen
|
// * 0x00E0 Clear Screen
|
||||||
@ -464,7 +465,7 @@ impl Chip8CpuInstructions {
|
|||||||
Chip8CpuInstructions::SeVxVy(x, y) => {
|
Chip8CpuInstructions::SeVxVy(x, y) => {
|
||||||
let lhs = input.registers.peek(*x as u8);
|
let lhs = input.registers.peek(*x as u8);
|
||||||
let rhs = input.registers.peek(*y as u8);
|
let rhs = input.registers.peek(*y as u8);
|
||||||
// println!("COMPARING [{lhs}] to [{rhs}]");
|
|
||||||
if lhs == rhs {
|
if lhs == rhs {
|
||||||
input.registers.advance_pc();
|
input.registers.advance_pc();
|
||||||
}
|
}
|
||||||
@ -535,9 +536,14 @@ impl Chip8CpuInstructions {
|
|||||||
// If Vy > Vx, then VF is set to 1, otherwise 0. Then Vx is subtracted from Vy, and the results stored in Vx.
|
// If Vy > Vx, then VF is set to 1, otherwise 0. Then Vx is subtracted from Vy, and the results stored in Vx.
|
||||||
let y_register = input.registers.peek(*y as u8);
|
let y_register = input.registers.peek(*y as u8);
|
||||||
let x_register = input.registers.peek(*x as u8);
|
let x_register = input.registers.peek(*x as u8);
|
||||||
let new_value = if y_register > x_register { 1 } else { 0 };
|
let new_value = if y_register <= x_register { 1 } else { 0 };
|
||||||
|
let value_to_poke = if y_register <= x_register {
|
||||||
|
((y_register as u16 + 256) - x_register as u16) as u8
|
||||||
|
} else {
|
||||||
|
y_register - x_register
|
||||||
|
};
|
||||||
input.registers.poke(0xf, new_value);
|
input.registers.poke(0xf, new_value);
|
||||||
input.registers.poke(*x as u8, x_register - y_register);
|
input.registers.poke(*x as u8, value_to_poke);
|
||||||
}
|
}
|
||||||
|
|
||||||
Chip8CpuInstructions::ShlVxVy(x, _) => {
|
Chip8CpuInstructions::ShlVxVy(x, _) => {
|
||||||
@ -591,31 +597,42 @@ impl Chip8CpuInstructions {
|
|||||||
}
|
}
|
||||||
Chip8CpuInstructions::DrawVxVyNibble(x, y, n) => {
|
Chip8CpuInstructions::DrawVxVyNibble(x, y, n) => {
|
||||||
// Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision.
|
// Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision.
|
||||||
|
|
||||||
|
|
||||||
// The interpreter reads n bytes from memory, starting at the address stored in I.
|
// The interpreter reads n bytes from memory, starting at the address stored in I.
|
||||||
|
|
||||||
|
|
||||||
// These bytes are then displayed as sprites on screen at coordinates (Vx, Vy).
|
// These bytes are then displayed as sprites on screen at coordinates (Vx, Vy).
|
||||||
// Sprites are XORed onto the existing screen.
|
// Sprites are XORed onto the existing screen.
|
||||||
// If this causes any pixels to be erased, VF is set to 1,
|
// If this causes any pixels to be erased, VF is set to 1,
|
||||||
// otherwise it is set to 0.
|
// otherwise it is set to 0.
|
||||||
// If the sprite is positioned so part of it is outside the coordinates of the display,
|
// If the sprite is positioned so part of it is outside the coordinates of the display,
|
||||||
// it wraps around to the opposite side of the screen.
|
// it wraps around to the opposite side of the screen.
|
||||||
//
|
|
||||||
// read nibble bytes from memory starting at I
|
|
||||||
|
|
||||||
let start_position = input.registers.peek_i();
|
let source_memory_offset = input.registers.peek_i();
|
||||||
|
|
||||||
|
let x_offset = input.registers.peek(*x as u8);
|
||||||
|
let y_offset = input.registers.peek(*y as u8);
|
||||||
|
|
||||||
|
let target_memory_offset = x_offset * 64 + y_offset;
|
||||||
|
|
||||||
|
println!("CHIP8CPUINSTRUCTION:DRAWVXNIBBLE -> STARTING AT {source_memory_offset} WRITING TO {target_memory_offset}");
|
||||||
let num_bytes_to_read = *n;
|
let num_bytes_to_read = *n;
|
||||||
for i in start_position..start_position + num_bytes_to_read {
|
println!("CHIP8CPUINSTRUCTION:DRAWVXNIBBLE -> PREPARING TO READ {num_bytes_to_read} BYTES FROM MEMORY TO VIDEO");
|
||||||
// let current_byte = input.memory[i as usize];
|
for byte_index in 0..num_bytes_to_read {
|
||||||
println!("READ BYTE [0x{i:2x}]");
|
let current_byte = input.memory.peek(byte_index + source_memory_offset);
|
||||||
|
println!("CHIP8CPUINSTRUCTION:DRAWVXNIBBLE -> READ BYTE [0x{byte_index:2x}]\t{current_byte:02x}\t{current_byte:08b}");
|
||||||
|
for bit_index in 0..8 {
|
||||||
|
let data_offset = ((x_offset as u16 + byte_index) * 64 ) + (y_offset + bit_index) as u16;
|
||||||
|
let current_bit = (current_byte.shr(7 - bit_index) & 0x1u8) == 0x1u8;
|
||||||
|
input.video_memory.poke(data_offset, current_bit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("PPOOSSTT -> CHIP8CPUINSTRUCTION:DRAWVXNIBBLE -> {}", input.video_memory.format_as_string());
|
||||||
|
|
||||||
let mut did_change: bool = false;
|
let mut did_change: bool = false;
|
||||||
|
|
||||||
for draw_x in 0..*n {
|
|
||||||
// let mut new_value = input.memory[(input.i_register + draw_x) as usize];
|
|
||||||
//for draw_y in 0..8 {
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
if did_change {
|
if did_change {
|
||||||
input.registers.poke(0xf, 1u8);
|
input.registers.poke(0xf, 1u8);
|
||||||
} else {
|
} else {
|
||||||
@ -657,6 +674,7 @@ impl Chip8CpuInstructions {
|
|||||||
// Wait for a key press, store the value of the key in Vx.
|
// Wait for a key press, store the value of the key in Vx.
|
||||||
//
|
//
|
||||||
// All execution stops until a key is pressed, then the value of that key is stored in Vx.
|
// All execution stops until a key is pressed, then the value of that key is stored in Vx.
|
||||||
|
input.state = WaitingForKey;
|
||||||
}
|
}
|
||||||
Chip8CpuInstructions::LdDtVx(source_register) => {
|
Chip8CpuInstructions::LdDtVx(source_register) => {
|
||||||
// Fx15 - LD DT, Vx
|
// Fx15 - LD DT, Vx
|
||||||
@ -683,13 +701,39 @@ impl Chip8CpuInstructions {
|
|||||||
// Fx29 - LD F, Vx
|
// Fx29 - LD F, Vx
|
||||||
// Set I = location of sprite for digit Vx.
|
// Set I = location of sprite for digit Vx.
|
||||||
//
|
//
|
||||||
// The value of I is set to the location for the hexadecimal sprite corresponding to the value of Vx. See section 2.4, Display, for more information on the Chip-8 hexadecimal font.
|
// The value of I is set to the location for the hexadecimal sprite corresponding
|
||||||
|
// to the value of Vx. See section 2.4, Display, for more information on
|
||||||
|
// the Chip-8 hexadecimal font.
|
||||||
|
|
||||||
|
let to_offset = input.registers.peek(*x as u8) - 1;
|
||||||
|
let real_offset = to_offset as u16 * 5;
|
||||||
|
input.registers.poke_i(real_offset as u16);
|
||||||
}
|
}
|
||||||
Chip8CpuInstructions::LdBVx(x) => {
|
Chip8CpuInstructions::LdBVx(x) => {
|
||||||
// Fx33 - LD B, Vx
|
// Fx33 - LD B, Vx
|
||||||
// Store BCD representation of Vx in memory locations I, I+1, and I+2.
|
// Store BCD representation of Vx in memory locations I, I+1, and I+2.
|
||||||
//
|
//
|
||||||
// The interpreter takes the decimal value of Vx, and places the hundreds digit in memory at location in I, the tens digit at location I+1, and the ones digit at location I+2.
|
// The interpreter takes the decimal value of Vx, and places the hundreds
|
||||||
|
// digit in memory at location in I, the tens digit at location I+1,
|
||||||
|
// and the ones digit at location I+2.
|
||||||
|
|
||||||
|
let to_convert = input.registers.peek(*x as u8);
|
||||||
|
|
||||||
|
// how many hundreds
|
||||||
|
let hundreds = to_convert / 100;
|
||||||
|
// how many tens, minus the hundreds
|
||||||
|
let tens = (to_convert / 10) - (hundreds * 10);
|
||||||
|
// whats the leftover when dividing by 10
|
||||||
|
let units = to_convert % 10;
|
||||||
|
|
||||||
|
// Convert to BCD
|
||||||
|
let result = ((hundreds as u16) << 8) | units as u16;
|
||||||
|
(tens << 4) | units;
|
||||||
|
// write them to the memory pointed to by I, I+1, and I+2
|
||||||
|
let target_start_offset = input.registers.peek_i();
|
||||||
|
input.memory.poke(target_start_offset, hundreds);
|
||||||
|
input.memory.poke(target_start_offset + 1, tens);
|
||||||
|
input.memory.poke(target_start_offset + 2, units);
|
||||||
}
|
}
|
||||||
Chip8CpuInstructions::LdIVx(x) => {
|
Chip8CpuInstructions::LdIVx(x) => {
|
||||||
// Store registers V0 through Vx in memory starting at location I.
|
// Store registers V0 through Vx in memory starting at location I.
|
||||||
@ -705,9 +749,14 @@ impl Chip8CpuInstructions {
|
|||||||
//
|
//
|
||||||
// The interpreter reads values from memory starting at location I into registers V0 through Vx.
|
// The interpreter reads values from memory starting at location I into registers V0 through Vx.
|
||||||
let offset = input.registers.peek_i();
|
let offset = input.registers.peek_i();
|
||||||
|
debug!("STARTING TO READ AT {offset:03x}");
|
||||||
let num_loops = input.registers.peek(*x as u8);
|
let num_loops = input.registers.peek(*x as u8);
|
||||||
|
debug!("WILL READ {num_loops:x} BYTES");
|
||||||
for index in 0..num_loops {
|
for index in 0..num_loops {
|
||||||
input.registers.poke(index, input.memory.peek(index as u16 + offset));
|
let src_value = input.memory.peek(index as u16 + offset);
|
||||||
|
|
||||||
|
input.registers.poke(index, src_value);
|
||||||
|
debug!("POKING {index} with {src_value}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Chip8CpuInstructions::XXXXERRORINSTRUCTION => {}
|
Chip8CpuInstructions::XXXXERRORINSTRUCTION => {}
|
||||||
@ -719,6 +768,9 @@ impl Chip8CpuInstructions {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use dimensioned::typenum::assert_type;
|
||||||
|
use ratatui::crossterm::execute;
|
||||||
|
use crate::chip8::system_memory::{CHIP8FONT_0, CHIP8FONT_1, CHIP8FONT_9};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1019,6 +1071,7 @@ mod test {
|
|||||||
assert_eq!(x.registers.peek(0xf), 1);
|
assert_eq!(x.registers.peek(0xf), 1);
|
||||||
assert_eq!(x.registers.peek_pc(), 0x206);
|
assert_eq!(x.registers.peek_pc(), 0x206);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ldi_addr_test() {
|
fn ldi_addr_test() {
|
||||||
let mut x = Chip8Computer::new();
|
let mut x = Chip8Computer::new();
|
||||||
@ -1035,9 +1088,10 @@ mod test {
|
|||||||
Chip8CpuInstructions::JpV0Addr(0x100).execute(&mut x);
|
Chip8CpuInstructions::JpV0Addr(0x100).execute(&mut x);
|
||||||
assert_eq!(x.registers.peek_pc(), 0x1FF);
|
assert_eq!(x.registers.peek_pc(), 0x1FF);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cls_test() {
|
fn cls_test() {
|
||||||
let mut x = Chip8Computer::new();
|
let mut x = Chip8Computer::new();
|
||||||
Chip8CpuInstructions::CLS.execute(&mut x);
|
Chip8CpuInstructions::CLS.execute(&mut x);
|
||||||
assert_eq!(x.registers.peek_pc(), 0x202);
|
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||||
}
|
}
|
||||||
@ -1065,39 +1119,39 @@ mod test {
|
|||||||
assert_eq!(x.registers.peek_i(), 0xacc);
|
assert_eq!(x.registers.peek_i(), 0xacc);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ldstvt_test() {
|
fn ldstvt_test() {
|
||||||
let mut x = Chip8Computer::new();
|
let mut x = Chip8Computer::new();
|
||||||
Chip8CpuInstructions::LdVxByte(0x01, 0xf0).execute(&mut x);
|
Chip8CpuInstructions::LdVxByte(0x01, 0xf0).execute(&mut x);
|
||||||
Chip8CpuInstructions::LdStVx(0x01).execute(&mut x);
|
Chip8CpuInstructions::LdStVx(0x01).execute(&mut x);
|
||||||
assert_eq!(x.sound_timer.current(), 0xf0);
|
assert_eq!(x.sound_timer.current(), 0xf0);
|
||||||
x.sound_timer.tick();
|
x.sound_timer.tick();
|
||||||
x.sound_timer.tick();
|
x.sound_timer.tick();
|
||||||
x.sound_timer.tick();
|
x.sound_timer.tick();
|
||||||
assert_eq!(x.sound_timer.current(), 0xed);
|
assert_eq!(x.sound_timer.current(), 0xed);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rnd_vx_byte_text() {
|
fn rnd_vx_byte_text() {
|
||||||
let mut x = Chip8Computer::new();
|
let mut x = Chip8Computer::new();
|
||||||
Chip8CpuInstructions::RndVxByte(0x1, 0x0f).execute(&mut x);
|
Chip8CpuInstructions::RndVxByte(0x1, 0x0f).execute(&mut x);
|
||||||
let new_value = x.registers.peek(0x1);
|
let new_value = x.registers.peek(0x1);
|
||||||
assert!(new_value < 0x10);
|
assert!(new_value < 0x10);
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
#[test]
|
#[test]
|
||||||
fn skp_vx_test() {
|
fn skp_vx_test() {
|
||||||
let mut x = Chip8Computer::new();
|
let mut x = Chip8Computer::new();
|
||||||
x.keypad.push_key(0x1);
|
x.keypad.push_key(0x1);
|
||||||
Chip8CpuInstructions::LdVxByte(0x1, 0x1).execute(&mut x);
|
Chip8CpuInstructions::LdVxByte(0x1, 0x1).execute(&mut x);
|
||||||
Chip8CpuInstructions::SkpVx(0x1).execute(&mut x);
|
Chip8CpuInstructions::SkpVx(0x1).execute(&mut x);
|
||||||
assert_eq!(x.registers.peek_pc(), 0x208);
|
assert_eq!(x.registers.peek_pc(), 0x208);
|
||||||
x.keypad.release_key(0x1);
|
x.keypad.release_key(0x1);
|
||||||
Chip8CpuInstructions::SkpVx(0x1).execute(&mut x);
|
Chip8CpuInstructions::SkpVx(0x1).execute(&mut x);
|
||||||
assert_eq!(x.registers.peek_pc(), 0x20A);
|
assert_eq!(x.registers.peek_pc(), 0x20A);
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_vx_byte_test() {
|
fn add_vx_byte_test() {
|
||||||
@ -1139,7 +1193,7 @@ mod test {
|
|||||||
Chip8CpuInstructions::LdVxByte(0x1, 0x10).execute(&mut x);
|
Chip8CpuInstructions::LdVxByte(0x1, 0x10).execute(&mut x);
|
||||||
Chip8CpuInstructions::LdDtVx(0x1).execute(&mut x);
|
Chip8CpuInstructions::LdDtVx(0x1).execute(&mut x);
|
||||||
assert_eq!(x.delay_timer.current(), 0x10);
|
assert_eq!(x.delay_timer.current(), 0x10);
|
||||||
for i in 0..0x20 {
|
for i in 0..0x20 {
|
||||||
x.delay_timer.tick();
|
x.delay_timer.tick();
|
||||||
}
|
}
|
||||||
assert_eq!(x.delay_timer.current(), 0);
|
assert_eq!(x.delay_timer.current(), 0);
|
||||||
@ -1157,4 +1211,234 @@ mod test {
|
|||||||
assert_eq!(x.registers.peek(0x1), 0xed);
|
assert_eq!(x.registers.peek(0x1), 0xed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn subn_vx_vy_test() {
|
||||||
|
// This instruction subtracts the value in
|
||||||
|
// register Vx from the value in register Vy and stores the result in register Vx.
|
||||||
|
// The subtraction is performed as follows: Vx = Vy - Vx. If Vy is less than Vx,
|
||||||
|
// the result will wrap around (due to the 8-bit nature of the registers).
|
||||||
|
// The carry flag (VF) is set to 1 if there is no borrow (i.e., Vy is greater
|
||||||
|
// than or equal to Vx), and it is set to 0 if there is a borrow.
|
||||||
|
|
||||||
|
|
||||||
|
let mut x = Chip8Computer::new();
|
||||||
|
Chip8CpuInstructions::LdVxByte(0x1, 0xa0).execute(&mut x);
|
||||||
|
Chip8CpuInstructions::LdVxByte(0x2, 0xab).execute(&mut x);
|
||||||
|
Chip8CpuInstructions::SubnVxVy(0x1, 0x2).execute(&mut x);
|
||||||
|
|
||||||
|
// expect the result to be 0x0b
|
||||||
|
assert_eq!(x.registers.peek(0x1), 0x0b);
|
||||||
|
// expect the vf register to be set to 1 as there was overflow
|
||||||
|
assert_eq!(x.registers.peek(0xf), 0x0);
|
||||||
|
|
||||||
|
let mut x = Chip8Computer::new();
|
||||||
|
Chip8CpuInstructions::LdVxByte(0x1, 0xab).execute(&mut x);
|
||||||
|
Chip8CpuInstructions::LdVxByte(0x2, 0xa0).execute(&mut x);
|
||||||
|
Chip8CpuInstructions::SubnVxVy(0x1, 0x2).execute(&mut x);
|
||||||
|
|
||||||
|
// expect the result to be 11110101, -0xB, -11, 245, 0xF5
|
||||||
|
assert_eq!(x.registers.peek(0x1), 0xf5);
|
||||||
|
assert_eq!(x.registers.peek(0xf), 0x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shl_vx_vy_test() {
|
||||||
|
// 8xyE - SHL Vx {, Vy}
|
||||||
|
// Set Vx = Vx SHL 1.
|
||||||
|
//
|
||||||
|
// If the most-significant bit of Vx is 1, then VF is set to 1, otherwise to 0. Then Vx is multiplied by 2.
|
||||||
|
|
||||||
|
let mut x = Chip8Computer::new();
|
||||||
|
Chip8CpuInstructions::LdVxByte(0x1, 0b00100000).execute(&mut x);
|
||||||
|
Chip8CpuInstructions::ShlVxVy(0x1, 0x1).execute(&mut x);
|
||||||
|
assert_eq!(x.registers.peek(0x1), 0b01000000);
|
||||||
|
assert_eq!(x.registers.peek(0xf), 0x0);
|
||||||
|
|
||||||
|
|
||||||
|
let mut x = Chip8Computer::new();
|
||||||
|
Chip8CpuInstructions::LdVxByte(0x1, 0b10101010).execute(&mut x);
|
||||||
|
Chip8CpuInstructions::ShlVxVy(0x1, 0x1).execute(&mut x);
|
||||||
|
assert_eq!(x.registers.peek(0x1), 0b01010100);
|
||||||
|
assert_eq!(x.registers.peek(0xf), 0x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ld_f_vx_test() {
|
||||||
|
// Fx29 - LD F, Vx
|
||||||
|
// Set I = location of sprite for digit Vx.
|
||||||
|
//
|
||||||
|
// The value of I is set to the location for the hexadecimal sprite corresponding to the value of Vx. See section 2.4, Display, for more information on the Chip-8 hexadecimal font.
|
||||||
|
let mut x = Chip8Computer::new();
|
||||||
|
// target_sprite = 2
|
||||||
|
// target_offset = 0x5
|
||||||
|
Chip8CpuInstructions::LdVxByte(0x1, 0x2).execute(&mut x);
|
||||||
|
Chip8CpuInstructions::LdFVx(0x1).execute(&mut x);
|
||||||
|
|
||||||
|
assert_eq!(x.registers.peek_i(), 0x5);
|
||||||
|
|
||||||
|
Chip8CpuInstructions::LdVxByte(0x1, 0x6).execute(&mut x);
|
||||||
|
Chip8CpuInstructions::LdFVx(0x1).execute(&mut x);
|
||||||
|
assert_eq!(x.registers.peek_i(), 25);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ld_b_vx_test() {
|
||||||
|
// Fx33 - LD B, Vx
|
||||||
|
// Store BCD representation of Vx in memory locations I, I+1, and I+2.
|
||||||
|
//
|
||||||
|
// The interpreter takes the decimal value of Vx, and places the hundreds digit
|
||||||
|
// in memory at location in I, the tens digit at location I+1,
|
||||||
|
// and the ones digit at location I+2.
|
||||||
|
let mut x = Chip8Computer::new();
|
||||||
|
|
||||||
|
// load the value 123 (0x7b)
|
||||||
|
Chip8CpuInstructions::LdVxByte(0x1, 0x7b).execute(&mut x);
|
||||||
|
// set I to 0x500
|
||||||
|
Chip8CpuInstructions::LdIAddr(0x500).execute(&mut x);
|
||||||
|
;
|
||||||
|
Chip8CpuInstructions::LdBVx(0x1).execute(&mut x);
|
||||||
|
assert_eq!(x.memory.peek(0x500), 0x1);
|
||||||
|
assert_eq!(x.memory.peek(0x501), 0x2);
|
||||||
|
assert_eq!(x.memory.peek(0x502), 0x3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ld_i_vx_test() {
|
||||||
|
// Store registers V0 through Vx in memory starting at location I.
|
||||||
|
//
|
||||||
|
// The interpreter copies the values of registers V0 through Vx into memory,
|
||||||
|
// starting at the address in I.
|
||||||
|
let mut x = Chip8Computer::new();
|
||||||
|
|
||||||
|
// Load Registers.
|
||||||
|
let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef];
|
||||||
|
for (idx, val) in to_load.iter().enumerate() {
|
||||||
|
x.registers.poke(idx as u8, *val);
|
||||||
|
}
|
||||||
|
x.registers.poke_i(0x500);
|
||||||
|
|
||||||
|
Chip8CpuInstructions::LdIVx(to_load.len() as u16).execute(&mut x);
|
||||||
|
|
||||||
|
// Verify the values are in memory from 0x500 to 0x507
|
||||||
|
for (idx, value) in to_load.iter().enumerate() {
|
||||||
|
assert_eq!(x.memory.peek(0x500 + idx as u16), *value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ld_vx_i_test() {
|
||||||
|
// Read registers V0 through Vx from memory starting at location I.
|
||||||
|
//
|
||||||
|
// The interpreter reads values from memory starting at location I into registers V0 through Vx.
|
||||||
|
|
||||||
|
let mut x = Chip8Computer::new();
|
||||||
|
|
||||||
|
let base_offset = 0x500;
|
||||||
|
let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef];
|
||||||
|
|
||||||
|
// start by setting values in memory
|
||||||
|
for (idx, memory) in to_load.iter().enumerate() {
|
||||||
|
let target_address = base_offset + idx;
|
||||||
|
let target_value = *memory;
|
||||||
|
x.memory.poke(target_address as u16, target_value);
|
||||||
|
}
|
||||||
|
// where to load from
|
||||||
|
x.registers.poke_i(0x500);
|
||||||
|
// how much to load
|
||||||
|
x.registers.poke(0x0, to_load.len() as u8);
|
||||||
|
|
||||||
|
// then copying them values memory to registers
|
||||||
|
Chip8CpuInstructions::LdVxI(0x0).execute(&mut x);
|
||||||
|
|
||||||
|
// now check that we have the right values in our registers
|
||||||
|
for (idx, value) in to_load.iter().enumerate() {
|
||||||
|
assert_eq!(x.registers.peek(idx as u8), *value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn Sknkpvx_test() {
|
||||||
|
// ExA1 - SKNP Vx
|
||||||
|
// Skip next instruction if key with the value of Vx is not pressed.
|
||||||
|
//
|
||||||
|
// Checks the keyboard,
|
||||||
|
// and if the key corresponding to the value of Vx is currently in the up position,
|
||||||
|
// PC is increased by 2.
|
||||||
|
let mut x = Chip8Computer::new();
|
||||||
|
x.keypad.push_key(0x5);
|
||||||
|
x.registers.poke(0x1, 0x5);
|
||||||
|
Chip8CpuInstructions::SnkpVx(0x1).execute(&mut x);
|
||||||
|
assert_eq!(x.registers.peek_pc(), 0x204);
|
||||||
|
|
||||||
|
x.keypad.release_key(0x5);
|
||||||
|
Chip8CpuInstructions::SnkpVx(0x1).execute(&mut x);
|
||||||
|
assert_eq!(x.registers.peek_pc(), 0x206);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn skpvx_test() {
|
||||||
|
// Ex9E - SKP Vx
|
||||||
|
// Skip next instruction if key with the value of Vx is pressed.
|
||||||
|
//
|
||||||
|
// Checks the keyboard, and if the key corresponding to the value of Vx is currently in the down position, PC is increased by 2.
|
||||||
|
let mut x = Chip8Computer::new();
|
||||||
|
x.keypad.push_key(0x5);
|
||||||
|
x.registers.poke(0x1, 0x5);
|
||||||
|
Chip8CpuInstructions::SkpVx(0x1).execute(&mut x);
|
||||||
|
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||||
|
|
||||||
|
x.keypad.release_key(0x5);
|
||||||
|
Chip8CpuInstructions::SkpVx(0x1).execute(&mut x);
|
||||||
|
assert_eq!(x.registers.peek_pc(), 0x204);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ldvxk_test() {
|
||||||
|
println!("THIS TEST DOES NOT REALLY DO ANYTHING AS IT DEPENDS ON THE LOCAL IMPLEMENTATION");
|
||||||
|
println!("OF HOW TO DELAY AND GET KEYBOARD INPUTS. THIS EXPECTS THAT THE SYSTEM GOES INTO");
|
||||||
|
println!("ITS HOLDING STATE AND ONCE THE KEY IS PRESSED THE KEYBOARD 'PRESS_KEY' IS COMPLETED");
|
||||||
|
println!("THE PC IS ALREADY ADVANCED AT THIS POINT SO THE SKPVX OR WHATEVER CAN HANDLE IT");
|
||||||
|
let mut x = Chip8Computer::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn draw_nibble_vx_vy_n_test() {
|
||||||
|
let mut x = Chip8Computer::new();
|
||||||
|
let x_register = 0x1;
|
||||||
|
let y_register = 0x2;
|
||||||
|
let x_offset = 1;
|
||||||
|
let y_offset = 2;
|
||||||
|
|
||||||
|
// use the font characters to draw to video memory
|
||||||
|
// assert_eq!(x.memory.peek(0x0), CHIP8FONT_0[0]);
|
||||||
|
// assert_eq!(x.memory.peek(0x1), CHIP8FONT_0[1]);
|
||||||
|
// assert_eq!(x.memory.peek(0x2), CHIP8FONT_0[2]);
|
||||||
|
// assert_eq!(x.memory.peek(0x3), CHIP8FONT_0[3]);
|
||||||
|
// assert_eq!(x.memory.peek(0x4), CHIP8FONT_0[4]);
|
||||||
|
|
||||||
|
// now lets set the X and Y to 1,2
|
||||||
|
x.registers.poke(x_register, x_offset);
|
||||||
|
x.registers.poke(y_register, y_offset);
|
||||||
|
x.registers.poke_i(0x5);
|
||||||
|
// we are using 5 rows.
|
||||||
|
Chip8CpuInstructions::DrawVxVyNibble(x_register as u16, y_register as u16, 5).execute(&mut x);
|
||||||
|
|
||||||
|
// now check that video memory has the values at
|
||||||
|
// 1,2->1,9
|
||||||
|
// 2,2->2,9
|
||||||
|
// 3,2->3,9
|
||||||
|
// 4,2->4,9
|
||||||
|
// 5,2->5,9
|
||||||
|
// let byte_to_check = CHIP8FONT_0[0];
|
||||||
|
for row_in_sprite in 0..5 {
|
||||||
|
let row_data = CHIP8FONT_1[row_in_sprite];
|
||||||
|
for bit_in_byte in 0..8 {
|
||||||
|
let data_offset = (x_offset + row_in_sprite as u8) * 64 + (bit_in_byte + y_offset);
|
||||||
|
let should_be_set = (row_data.shr(bit_in_byte) & 0x1) == 1;
|
||||||
|
println!("DATA_OFFSET FOR {}x{} is {} when offsetting by {}x{} and should be {} working with byte {:08b}",
|
||||||
|
bit_in_byte, row_in_sprite, data_offset, x_offset, y_offset, should_be_set, row_data);
|
||||||
|
// assert_eq!(should_be_set, );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,24 +28,32 @@ impl Chip8Video {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn poke(&mut self, address: u16, new_value: bool) -> Self {
|
pub fn poke(&mut self, address: u16, new_value: bool) -> Self {
|
||||||
println!("POKING {new_value} AT {address}");
|
println!("**VIDEO** POKING {new_value} TO {address}");
|
||||||
self.memory[address as usize] = new_value;
|
self.memory[address as usize] = new_value;
|
||||||
self.to_owned()
|
self.to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn poke_byte(&mut self, first_address: u16, to_write: u8) -> Self {
|
pub fn poke_byte(&mut self, first_address: u16, to_write: u8) -> Self {
|
||||||
println!("PREPARING TO POKE {to_write:b} to {first_address:4x}");
|
|
||||||
for i in (0..8).rev() {
|
for i in (0..8).rev() {
|
||||||
let shifted = ((1 << i) & to_write) >> i;
|
let shifted = ((1 << i) & to_write) >> i;
|
||||||
//
|
//
|
||||||
let target_address = first_address + (7 - i);
|
let target_address = first_address + (7 - i);
|
||||||
let is_set = shifted == 1;
|
let is_set = shifted == 1;
|
||||||
println!("POKE {} with {} / {shifted:8b}", target_address, is_set);
|
|
||||||
self.poke(target_address, is_set);
|
self.poke(target_address, is_set);
|
||||||
}
|
}
|
||||||
self.to_owned()
|
self.to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn poke_sprite(&mut self, first_address: u16, to_write: Vec<u8>) -> Self {
|
||||||
|
let sprite_length = to_write.len();
|
||||||
|
|
||||||
|
for (index, byte) in to_write.iter().enumerate() {
|
||||||
|
let real_address = index * 64;
|
||||||
|
self.poke_byte(real_address as u16, *byte);
|
||||||
|
}
|
||||||
|
self.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn format_as_string(self) -> String {
|
pub fn format_as_string(self) -> String {
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
for row in 0..32 {
|
for row in 0..32 {
|
||||||
@ -65,9 +73,7 @@ impl Chip8Video {
|
|||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_sprite(&mut self, sprite_data: Vec<u8>, origin: (u8, u8)) {
|
|
||||||
debug!("Writing [{:?}] at [{}]x[{}]", sprite_data, origin.0, origin.1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Chip8Video {
|
impl Default for Chip8Video {
|
||||||
@ -219,15 +225,15 @@ mod test {
|
|||||||
assert!(v.peek(0x46));
|
assert!(v.peek(0x46));
|
||||||
assert!(v.peek(0x47));
|
assert!(v.peek(0x47));
|
||||||
|
|
||||||
// row 3 column 1
|
// row 3 column 1
|
||||||
assert!(!v.peek(0xC0));
|
assert!(!v.peek(0xC0));
|
||||||
assert!(v.peek(0xC1));
|
assert!(v.peek(0xC1));
|
||||||
assert!(!v.peek(0xC2));
|
assert!(!v.peek(0xC2));
|
||||||
assert!(v.peek(0xC3));
|
assert!(v.peek(0xC3));
|
||||||
assert!(!v.peek(0xC4));
|
assert!(!v.peek(0xC4));
|
||||||
assert!(v.peek(0xC5));
|
assert!(v.peek(0xC5));
|
||||||
assert!(!v.peek(0xC6));
|
assert!(!v.peek(0xC6));
|
||||||
assert!(v.peek(0xC7));
|
assert!(v.peek(0xC7));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,7 +257,6 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let test_offset = (x_offset * 64 + y_offset) as u16;
|
let test_offset = (x_offset * 64 + y_offset) as u16;
|
||||||
println!("TEST OFFSET = {test_offset}");
|
|
||||||
assert!(!v.peek(test_offset));
|
assert!(!v.peek(test_offset));
|
||||||
assert!(!v.peek(test_offset + 1));
|
assert!(!v.peek(test_offset + 1));
|
||||||
assert!(!v.peek(test_offset + 2));
|
assert!(!v.peek(test_offset + 2));
|
||||||
@ -270,16 +275,38 @@ mod test {
|
|||||||
assert!(v.peek(test_offset + 5));
|
assert!(v.peek(test_offset + 5));
|
||||||
assert!(v.peek(test_offset + 6));
|
assert!(v.peek(test_offset + 6));
|
||||||
assert!(v.peek(test_offset + 7));
|
assert!(v.peek(test_offset + 7));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn write_sprite_test() {
|
fn poke_sprite_test() {
|
||||||
|
let mut v = Chip8Video::default();
|
||||||
|
let to_poke = [
|
||||||
|
0b00000000,
|
||||||
|
0b11111111,
|
||||||
|
0b10101010,
|
||||||
|
0b01010101
|
||||||
|
];
|
||||||
|
|
||||||
|
v.poke_sprite(0x00, to_poke.into());
|
||||||
|
|
||||||
|
assert!(v.peek(0x40));
|
||||||
|
assert!(v.peek(0x41));
|
||||||
|
assert!(v.peek(0x42));
|
||||||
|
assert!(v.peek(0x43));
|
||||||
|
assert!(v.peek(0x44));
|
||||||
|
assert!(v.peek(0x45));
|
||||||
|
assert!(v.peek(0x46));
|
||||||
|
assert!(v.peek(0x47));
|
||||||
|
|
||||||
|
// row 3 column 1
|
||||||
|
assert!(!v.peek(0xC0));
|
||||||
|
assert!(v.peek(0xC1));
|
||||||
|
assert!(!v.peek(0xC2));
|
||||||
|
assert!(v.peek(0xC3));
|
||||||
|
assert!(!v.peek(0xC4));
|
||||||
|
assert!(v.peek(0xC5));
|
||||||
|
assert!(!v.peek(0xC6));
|
||||||
|
assert!(v.peek(0xC7));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_bit_set(to_check: u8, bit_index: u8) -> bool {
|
|
||||||
(to_check >> bit_index )& 0x1 == 0x1
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user