registers better integrated

more tests and docs to the executors
This commit is contained in:
Trevor Merritt 2024-09-25 11:26:09 -04:00
parent 0075c5ef7d
commit 7b36061268
6 changed files with 313 additions and 32 deletions

View File

@ -63,7 +63,7 @@ fn main() {
EmmaGui::system_controls(&mut system, ui);
EmmaGui::registers_view(&system, ui);
let active_instruction = system.pc;
let active_instruction = system.registers.peek_pc();
EmmaGui::hex_memory_display(system.memory.clone(), (0x100, 0x10), active_instruction as i16, ui);
EmmaGui::video_display(&system, ui);

View File

@ -69,18 +69,18 @@ impl EmmaGui {
.build(|| {
ui.text("Registers");
for i in 0..0x10 {
ui.text(format!("V{:X}: {}", i, system.registers[i as usize]));
ui.text(format!("V{:X}: {}", i, system.registers.peek(i)));
if i != 7 {
ui.same_line();
}
}
ui.text("");
ui.text(format!("I: {:03X}", system.i_register));
ui.text(format!("I: {:03X}", system.registers.peek_i()));
ui.same_line();
ui.text(format!("ST: {:02X}", system.sound_timer.current()));
ui.same_line();
ui.text(format!("DT: {:02X}", system.delay_timer.current()));
ui.text(format!("PC: {:02X}", system.pc));
ui.text(format!("PC: {:02X}", system.registers.peek_pc()));
ui.text(format!("SP: {:02X}", system.sp));
});
}

View File

@ -1,6 +1,7 @@
use log::{debug, error};
use crate::chip8::delay_timer::DelayTimer;
use crate::chip8::instructions::Chip8CpuInstructions::XXXXERRORINSTRUCTION;
use crate::chip8::keypad::Keypad;
use crate::chip8::registers::Chip8Registers;
use crate::chip8::sound_timer::SoundTimer;
use crate::chip8::util::InstructionUtil;
@ -19,6 +20,7 @@ pub struct Chip8Computer {
pub delay_timer: DelayTimer,
pub video_memory: Chip8Video,
pub state: Chip8CpuStates,
pub keypad: Keypad
}
impl Default for Chip8Computer {
@ -31,6 +33,7 @@ impl Default for Chip8Computer {
sound_timer: SoundTimer::new(),
delay_timer: DelayTimer::new(),
state: Chip8CpuStates::WaitingForInstruction,
keypad: Keypad::default()
}
}
}

View File

@ -436,21 +436,62 @@ impl Chip8CpuInstructions {
let lhs = input.registers.peek(*x as u8);
let rhs = input.registers.peek(*y as u8);
let working =(lhs + rhs) as i16;
let working = (lhs as i16 + rhs as i16) as i16;
if working > 255 {
input.registers.poke(0xf, 0x01);
}
input.registers.poke(*x as u8, working as u8);
}
Chip8CpuInstructions::SubVxVy(x,y) => {
// 8xy5 - SUB Vx, Vy
// Set Vx = Vx - Vy, set VF = NOT borrow.
//
// If Vx > Vy, then VF is set to 1, otherwise 0. Then Vy is subtracted from Vx, and the results stored in Vx.
let lhs = input.registers.peek(*x as u8);
let rhs = input.registers.peek(*y as u8);
input.registers.poke(*x as u8, lhs - rhs);
}
Chip8CpuInstructions::ShrVxVy(_, _) => {}
Chip8CpuInstructions::SubnVxVy(_, _) => {}
Chip8CpuInstructions::ShlVxVy(_, _) => {}
Chip8CpuInstructions::ShrVxVy(x, y) => {
// 8xy6 - SHR Vx {, Vy}
// Set Vx = Vx SHR 1.
//
// If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx is divided by 2.
let initial_value = input.registers.peek(*x as u8);
if 0xb1 & initial_value == 1 {
input.registers.poke(0xf, 1);
}
input.registers(x, initial_value.rotate_left(1));
}
Chip8CpuInstructions::SubnVxVy(x,y) => {
// 8xy7 - SUBN Vx, Vy
// Set Vx = Vy - Vx, set VF = NOT borrow.
//
// 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);
let x_register = input.registers.peek(x);
let new_value = if y_register > x_register { 1 } else { 0 };
input.registers.poke(0xf, new_value);
input.registers.poke(x, x_register - y_register);
}
Chip8CpuInstructions::ShlVxVy(x, y) => {
// 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 initial_value = input.registers.peek(*x as u8);
if 0x80 & initial_value == 0x80 {
input.registers.poke(0xf, 1);
}
input.registers(x, initial_value.rotate_left(1));
}
Chip8CpuInstructions::SneVxVy(vx_register, vy_register) => {
// 9xy0 - SNE Vx, Vy
// Skip next instruction if Vx != Vy.
//
// The values of Vx and Vy are compared, and if they are not equal, the program counter is increased by 2.
let x_reg_value = input.registers.peek(*vx_register as u8);
let y_reg_value = input.registers.peek(*vy_register as u8);
if x_reg_value != y_reg_value {
@ -458,15 +499,40 @@ impl Chip8CpuInstructions {
}
}
Chip8CpuInstructions::LdIAddr(new_index) => {
// Annn - LD I, addr
// Set I = nnn.
//
// The value of register I is set to nnn.
input.registers.poke_i(input.registers.peek(*new_index as u8) as u16);
}
// 0xBnnn Jump to nnn+V0
Chip8CpuInstructions::JpV0Addr(_) => {}
Chip8CpuInstructions::JpV0Addr(addr) => {
// Bnnn - JP V0, addr
// Jump to location nnn + V0.
//
// The program counter is set to nnn plus the value of V0.
let x_reg = input.registers.peek(0);
input.registers.poke_pc(x_reg + addr);
}
Chip8CpuInstructions::RndVxByte(x, byte) => {
// Cxkk - RND Vx, byte
// Set Vx = random byte AND kk.
//
// The interpreter generates a random number from 0 to 255, which is then ANDed with the value kk. The results are stored in Vx. See instruction 8xy2 for more information on AND.
let new_value: u8 = random() ;
input.registers.poke(*x as u8, (new_value & *byte as u8))
}
Chip8CpuInstructions::DrawVxVyNibble(x, y, n) => {
// 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.
// These bytes are then displayed as sprites on screen at coordinates (Vx, Vy).
// Sprites are XORed onto the existing screen.
// If this causes any pixels to be erased, VF is set to 1,
// otherwise it is set to 0.
// 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.
//
// read nibble bytes from memory starting at I
let mut did_change: bool = false;
@ -483,19 +549,89 @@ impl Chip8CpuInstructions {
input.registers.poke(0x10, 0u8);
}
}
Chip8CpuInstructions::SkpVx(_) => {}
Chip8CpuInstructions::SnkpVx(_) => {}
Chip8CpuInstructions::LdVxDt(_) => {}
Chip8CpuInstructions::LdVxK(_) => {}
Chip8CpuInstructions::LdDtVx(_) => {}
Chip8CpuInstructions::LdStVx(_) => {}
Chip8CpuInstructions::AddIVx(_) => {}
Chip8CpuInstructions::LdFVx(_) => {}
Chip8CpuInstructions::LdBVx(_) => {}
Chip8CpuInstructions::LdIVx(x) => {
input.registers.poke(*x as u8, input.registers.peek_i() as u8);
Chip8CpuInstructions::SkpVx(x) => {
// 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 key_to_check = input.registers.peek(x);
}
Chip8CpuInstructions::SnkpVx(x) => {
// 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.
}
Chip8CpuInstructions::LdVxDt(x) => {
// Fx07 - LD Vx, DT
// Set Vx = delay timer value.
//
// The value of DT is placed into Vx.
input.registers.poke(x, input.delay_timer.current());
}
Chip8CpuInstructions::LdVxK(x) => {
// Fx0A - LD Vx, K
// 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.
}
Chip8CpuInstructions::LdDtVx(new_time) => {
// Fx15 - LD DT, Vx
// Set delay timer = Vx.
//
// DT is set equal to the value of Vx.
input.delay_timer.set_timer(new_time);
}
Chip8CpuInstructions::LdStVx(new_time) => {
input.sound_timer.set_timer(new_time);
}
Chip8CpuInstructions::AddIVx(x) => {
// Fx1E - ADD I, Vx
// Set I = I + Vx.
//
// The values of I and Vx are added, and the results are stored in I.
let base = input.registers.peek_i();
let x_value = input.registers.peek(x);
input.registers.poke_i( base + x_value);
}
Chip8CpuInstructions::LdFVx(x) => {
// 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.
}
Chip8CpuInstructions::LdBVx(x) => {
// 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.
}
Chip8CpuInstructions::LdIVx(x) => {
// 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 offset = input.registers.peek_i();
for i in 0..x {
input.memory.poke(offset + i, input.registers.peek(i));
}
}
Chip8CpuInstructions::LdVxI(x) => {
// 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 offset = input.registers.peek_i();
let num_loops = input.registers.peek(x);
for index in 0..num_loops {
input.registers.poke(index, input.memory.peek(index + offset));
}
}
Chip8CpuInstructions::LdVxI(_) => {}
Chip8CpuInstructions::XXXXERRORINSTRUCTION => {}
};
*input
@ -513,7 +649,7 @@ mod test {
}
#[test]
fn encoder_test() {
fn encode_decode_test() {
assert_eq!(Chip8CpuInstructions::CLS.encode(), 0x00E0);
assert_eq!(Chip8CpuInstructions::RET.encode(), 0x00EE);
assert_eq!(Chip8CpuInstructions::SysAddr(0x123).encode(), 0x0123);
@ -549,10 +685,6 @@ mod test {
assert_eq!(Chip8CpuInstructions::LdBVx(0xd).encode(), 0xfd33);
assert_eq!(Chip8CpuInstructions::LdIVx(0xe).encode(), 0xfe55);
assert_eq!(Chip8CpuInstructions::LdVxI(0x3).encode(), 0xf365);
}
#[test]
fn decoder_test() {
assert!(matches!( Chip8CpuInstructions::decode(0x00E0u16), Chip8CpuInstructions::CLS));
assert!(matches!( Chip8CpuInstructions::decode(0x00EEu16), Chip8CpuInstructions::RET));
assert!(matches!(Chip8CpuInstructions::decode(0x0123), Chip8CpuInstructions::SysAddr(0x123)));
@ -703,20 +835,157 @@ mod test {
#[test]
fn AddVxByte_test() {
// 0x7xkk Set Vx = Vx + kk
let x = Chip8Computer::new();
let mut x = Chip8Computer::new();
Chip8CpuInstructions::LdVxByte(0x01, 0x01).execute(&mut x);
Chip8CpuInstructions::LdVxByte(0x02, 0x02).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x204);
Chip8CpuInstructions::AddVxVy(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(1), 0x03);
}
#[test]
fn LdVxVy_test() {
// 0x8xy0 Set value of Vy in Vx
let mut x = Chip8Computer::new();
Chip8CpuInstructions::LdVxByte(0x01, 0x01).execute(&mut x);
Chip8CpuInstructions::LdVxByte(0x02, 0x02).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x204);
assert_eq!(x.registers.peek(1), 0x01);
Chip8CpuInstructions::LdVxVy(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(1), 0x02);
assert_eq!(x.registers.peek_pc(), 0x206);
}
#[test]
fn LdVxVy_test() {}
fn OrVxVy_test() {
// 0x8xy1 Set Vx = Vx OR Vy
// 0b0101 0000 (0x50)
// | 0b0000 1010 (0x0A)
// 0b0101 1010 (0x5A)
let mut x = Chip8Computer::new();
Chip8CpuInstructions::LdVxByte(1, 0x50).execute(&mut x);
Chip8CpuInstructions::LdVxByte(2, 0x0A).execute(&mut x);
Chip8CpuInstructions::OrVxVy(1,2).execute(&mut x);
assert_eq!(x.registers.peek(1), 0x5A);
assert_eq!(x.registers.peek_pc(), 0x206);
}
#[test]
fn OrVxVy_test() {}
fn AndVxVy_test() {
// 0x8xy2 Set Vx = Vx AND Vy
// 0b1111 1100 (0xFC)
// & 0b1100 1010 (0xCA)
// 0b1100 1000 (0xC8)
let mut x = Chip8Computer::new();
Chip8CpuInstructions::LdVxByte(1, 0xFC).execute(&mut x);
Chip8CpuInstructions::LdVxByte(2, 0xCA).execute(&mut x);
Chip8CpuInstructions::AndVxVy(1,2).execute(&mut x);
assert_eq!(x.registers.peek(1), 0xC8);
assert_eq!(x.registers.peek_pc(), 0x206);
}
#[test]
fn AndVxVy_test() {}
fn XorVxVy_test() {
// 0x8xy3 Set Vx = Vx XOR Vy
// 0b1111 1100 (0xFC)
// ^ 0b1100 1010 (0xCA)
// 0b0011 0110 (0x36)
let mut x = Chip8Computer::new();
Chip8CpuInstructions::LdVxByte(1, 0xFC).execute(&mut x);
Chip8CpuInstructions::LdVxByte(2, 0xCA).execute(&mut x);
Chip8CpuInstructions::XorVxVy(1,2).execute(&mut x);
assert_eq!(x.registers.peek(1), 0x36);
assert_eq!(x.registers.peek_pc(), 0x206);
}
#[test]
fn XorVxVy_test() {}
fn AddVxVy_test() {
// 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry)
// T1 T2: Judgement Test
// 0x01 0xFF
// + 0x01 0x01
// 0x02 F0 0x00 F1
let mut x = Chip8Computer::new();
Chip8CpuInstructions::LdVxByte(0xf, 0x00).execute(&mut x);
Chip8CpuInstructions::LdVxByte(1, 0x01).execute(&mut x);
Chip8CpuInstructions::LdVxByte(2, 0x01).execute(&mut x);
Chip8CpuInstructions::AddVxVy(1, 2).execute(&mut x);
// T1
assert_eq!(x.registers.peek(0xf), 0);
assert_eq!(x.registers.peek(1), 2);
assert_eq!(x.registers.peek_pc(), 0x208);
let mut x = Chip8Computer::new();
Chip8CpuInstructions::LdVxByte(0xf, 0x00).execute(&mut x);
Chip8CpuInstructions::LdVxByte(0x1, 0xff).execute(&mut x);
Chip8CpuInstructions::LdVxByte(0x2, 0x01).execute(&mut x);
Chip8CpuInstructions::AddVxVy(1,2).execute(&mut x);
// T2
assert_eq!(x.registers.peek(0xf), 1);
assert_eq!(x.registers.peek(1), 0);
assert_eq!(x.registers.peek_pc(), 0x208)
}
#[test]
fn SubVxVy_test() {
/*
todo: this test sucks. dont have the borrow concept in here.
Set Vx = Vx - Vy, set VF = NOT borrow.
If Vx > Vy, then VF is set to 1, otherwise 0.
Then Vy is subtracted from Vx, and the results stored in Vx.
*/
let mut x = Chip8Computer::new();
Chip8CpuInstructions::LdVxByte(1, 0x10).execute(&mut x);
Chip8CpuInstructions::LdVxByte(2, 0x01).execute(&mut x);
Chip8CpuInstructions::LdVxByte(0xf, 0x00).execute(&mut x);
Chip8CpuInstructions::SubVxVy(0x1, 0x2).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x208);
assert_eq!(x.registers.peek(1), 0xF);
assert_eq!(x.registers.peek(0x10), 0);
}
fn ShrVxVy_test() {
/*
Set Vx = Vx SHR 1.
If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx is divided by 2.
*/
let x = Chip8Computer::new();
Chip8CpuInstructions::LdVxByte(0xf, 0x00);
Chip8CpuInstructions::LdVxByte(0x1, 0x08); // 0b0000 1000 (0x08)
Chip8CpuInstructions::LdVxByte(0x2, 0x2);
Chip8CpuInstructions::ShrVxVy(0x1, 0x2); // 0b0000 0010 (0x02) (Not Set)
assert_eq!(x.registers.peek(1), 0x02);
assert_eq!(x.registers.peek(0xf), 0);
assert_eq!(x.registers.peek_pc(), 0x206);
let x = Chip8Computer::new();
Chip8CpuInstructions::LdVxByte(0xf, 0x00);
Chip8CpuInstructions::LdVxByte(0x1, 0x09); // 0b0000 1001 (0x09)
Chip8CpuInstructions::LdVxByte(0x2, 0x2);
Chip8CpuInstructions::ShrVxVy(0x1, 0x2); // 0b0000 0010 (0x02) (Set)
assert_eq!(x.registers.peek(1), 0x02);
assert_eq!(x.registers.peek(0xf), 1);
assert_eq!(x.registers.peek_pc(), 0x206);
}
fn SneVxVy_test() {}
fn LdiAddr_test() {}
fn JpV0Addr_test() {}
fn RndVxByte_test() {}
fn DrawVxVyNibble_test() {}
fn SkpVx_test() {
}
fn SnKpVx_test() {
}
fn LdVxDt() {
}
fn LdVxK_test() {}
fn LdStVx_test() {}
fn LdIVx_test() {}
fn LdVxI_test() {}
}

View File

@ -3,10 +3,19 @@ use ratatui::{
widgets::Widget,
};
#[derive(Clone, Copy)]
pub struct Keypad {
keys: [bool; 0x10],
}
impl Default for Keypad {
fn default() -> Self {
Keypad {
keys: [ false; 16]
}
}
}
impl Keypad {
pub fn push_key(&mut self, key_index: u8) {
self.keys[key_index as usize] = true;

View File

@ -43,7 +43,7 @@ impl Chip8Registers {
self.registers[(register_number - 1) as usize] = value;
}
pub fn peek_pc(&mut self) -> u16 {
pub fn peek_pc(&self) -> u16 {
self.pc
}