my first schip rom works in my schip emulator.

BUGFIX: Corrects runaway after drawing in my first schip rom
scroll down, left, right all test with test rom
assembler now assembles to the expected output it seems
fixes incorrect loading of schip font to memory
replaces schip font from new chatgpt feedback
This commit is contained in:
Trevor Merritt 2024-11-05 10:02:19 -05:00
parent 434cf92414
commit 67ca71ccb7
39 changed files with 509 additions and 1052 deletions

File diff suppressed because one or more lines are too long

View File

@ -12,7 +12,7 @@ use super::{
system_memory::Chip8SystemMemory, video::Chip8Video,
};
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Chip8Computer {
pub num_cycles: i32,
pub memory: Chip8SystemMemory,
@ -43,15 +43,15 @@ impl Default for Chip8Computer {
}
impl Chip8Computer {
pub fn reset(&mut self) {
pub fn reset(&mut self, quirk_mode: QuirkMode) {
self.video_memory.reset();
self.num_cycles = 0;
self.registers.reset();
self.delay_timer.reset();
self.sound_timer.reset();
self.stack.reset();
self.memory.reset();
self.quirk_mode = QuirkMode::Chip8;
self.memory.reset(quirk_mode);
self.quirk_mode = quirk_mode;
}
pub fn dump_keypad_to_string(&self) -> String {
@ -108,7 +108,7 @@ impl Chip8Computer {
match self.state {
Chip8CpuStates::WaitingForInstruction => {
println!("Ticking sound, delay, video");
// println!("Ticking sound, delay, video");
self.sound_timer.tick();
self.delay_timer.tick();
self.video_memory.tick();

View File

@ -1,21 +1,22 @@
use std::thread;
use std::thread::sleep;
use std::time::{Duration, Instant};
use crate::chip8::computer::Chip8Computer;
use crate::chip8::cpu_states::Chip8CpuStates;
use crate::chip8::cpu_states::Chip8CpuStates::WaitingForInstruction;
use crate::chip8::quirk_modes::QuirkMode;
use std::thread;
use std::thread::sleep;
use std::time::{Duration, Instant};
pub enum ManagerDumpables {
Video,
Registers,
Keyboard
Keyboard,
}
pub struct Chip8ComputerManager {
core_should_run: bool,
one_step: bool,
core_cycle_timer: bool,
core_last_cycle_start: Instant,
computer: Chip8Computer
computer: Chip8Computer,
}
impl Default for Chip8ComputerManager {
@ -23,26 +24,27 @@ impl Default for Chip8ComputerManager {
Chip8ComputerManager {
core_should_run: false,
one_step: false,
core_cycle_timer: false,
core_last_cycle_start: Instant::now() ,
computer: Chip8Computer::new()
core_last_cycle_start: Instant::now(),
computer: Chip8Computer::new(),
}
}
}
impl Chip8ComputerManager {
pub fn reset(&mut self) {
pub fn quirks_mode(&self) -> QuirkMode {
self.computer.quirk_mode.clone()
}
pub fn reset(&mut self, mode: QuirkMode) {
self.one_step = false;
self.core_should_run = false;
self.computer.reset();
self.computer.reset(mode);
}
pub fn new() -> Chip8ComputerManager {
let core_handle = thread::spawn(move || {
loop {
let _ = thread::spawn(move || loop {
let start_time = Instant::now();
let sleep_time = Instant::now().duration_since(start_time).as_millis();
sleep(Duration::from_millis((16 - sleep_time) as u64));
}
});
Chip8ComputerManager::default()
@ -64,7 +66,7 @@ impl Chip8ComputerManager {
&self.computer
}
pub fn tick( &mut self) -> bool {
pub fn tick(&mut self) -> bool {
// println!("STARTING TICK");
let mut did_tick: bool = false;
if self.one_step | self.core_should_run {
@ -112,21 +114,15 @@ impl Chip8ComputerManager {
}
pub fn load_new_program_to_system_memory(&mut self, bytes_to_load: Vec<u8>) {
self.reset();
self.reset(self.computer.quirk_mode.clone());
self.computer.load_bytes_to_memory(0x200, &bytes_to_load);
}
pub fn dump_to_string(&self, dump_type: ManagerDumpables) -> String {
match dump_type {
ManagerDumpables::Video => {
self.computer.video_memory.format_as_string()
}
ManagerDumpables::Registers => {
self.computer.registers.format_as_string()
}
ManagerDumpables::Keyboard => {
self.computer.keypad.format_as_string()
}
ManagerDumpables::Video => self.computer.video_memory.format_as_string(),
ManagerDumpables::Registers => self.computer.registers.format_as_string(),
ManagerDumpables::Keyboard => self.computer.keypad.format_as_string(),
}
}
}

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Default, Serialize, Deserialize)]
#[derive(Clone, Copy, Default, Serialize, Deserialize, Debug)]
pub enum Chip8CpuStates {
#[default]
WaitingForInstruction,

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Serialize, Deserialize)]
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
pub struct DelayTimer {
counter: u8,
}

View File

@ -7,6 +7,7 @@ use crate::constants::*;
use log::debug;
use rand::Rng;
use serde::{Deserialize, Serialize};
use std::ascii::AsciiExt;
use std::fmt::{Debug, Display, Formatter};
use std::ops::BitAnd;
use std::time::Instant;
@ -270,6 +271,11 @@ pub enum Chip8CpuInstructions {
/// scroll screen content down N pixel, in XO-CHIP only selected bit
/// planes are scrolled (Quirks are HP48 specific)
SCU(u8),
/// 0xNNNN
///
/// data word
/// used for specifying data to be used in system memory
DW(u16),
}
impl Chip8CpuInstructions {
@ -322,6 +328,7 @@ impl Chip8CpuInstructions {
JPX(_, _) => INST_JPX,
XXXXERRORINSTRUCTION => "XX ERROR XX",
SCU(_) => INST_SCU,
DW(_) => INST_DW,
}
}
@ -331,7 +338,8 @@ impl Chip8CpuInstructions {
let addr_for_display = (*x as u16) << 8 | *addr;
format!("0x{x:02x}, 0x{addr_for_display:04x}")
}
Chip8CpuInstructions::SYS(addr)
Chip8CpuInstructions::DW(addr)
| Chip8CpuInstructions::SYS(addr)
| Chip8CpuInstructions::JPI(addr)
| Chip8CpuInstructions::JPA(addr)
| Chip8CpuInstructions::LDIA(addr)
@ -406,7 +414,7 @@ impl Chip8CpuInstructions {
pub fn from_str(input: &str) -> Chip8CpuInstructions {
let mut parts = input.split(" ");
// print!("THERE ARE {} PARTS", parts.clone().count());
let first_part = parts.next().unwrap_or("");
let first_part = parts.next().unwrap_or("").to_ascii_uppercase();
// take the next value...
// ...strip off the extra...
// ...convert it to an integer from base 16
@ -414,6 +422,7 @@ impl Chip8CpuInstructions {
parts
.next()
.unwrap_or("0")
.to_ascii_lowercase()
.trim_start_matches("0x")
.trim_end_matches(","),
16,
@ -423,6 +432,7 @@ impl Chip8CpuInstructions {
parts
.next()
.unwrap_or("0")
.to_ascii_lowercase()
.trim_start_matches("0x")
.trim_end_matches(","),
16,
@ -432,13 +442,17 @@ impl Chip8CpuInstructions {
parts
.next()
.unwrap_or("0")
.to_ascii_lowercase()
.trim_start_matches("0x")
.trim_end_matches(","),
16,
)
.unwrap_or(0);
// println!("\tFirst part is {:?} / {:?} / {:?} / {:?}", first_part, param1 ,param2 ,param3);
match first_part {
println!(
"\tFirst part is {:?} / {:?} / {:?} / {:?}",
first_part, param1, param2, param3
);
match first_part.as_str() {
INST_ADDI => ADDI(param1 as u8),
INST_ADD => ADD(param1 as u8, param2 as u8),
INST_CLS => CLS,
@ -483,6 +497,7 @@ impl Chip8CpuInstructions {
INST_LDIS => LDIS(param1 as u8),
INST_LDD => LDD(param1 as u8),
INST_JPX => JPX(param1 as u8, param2),
INST_DW => DW(param1 as u16),
_ => XXXXERRORINSTRUCTION,
}
}
@ -571,8 +586,9 @@ impl Chip8CpuInstructions {
Chip8CpuInstructions::LDF2(x_register) => 0xF030 | ((*x_register as u16) << 8),
Chip8CpuInstructions::STR(x_register) => 0xF075 | ((*x_register as u16) << 8),
Chip8CpuInstructions::LIDR(x_register) => 0xF085 | ((*x_register as u16) << 8),
XXXXERRORINSTRUCTION => 0xFFFF,
SCU(x_register) => 0x00D0 | (*x_register as u16),
DW(address) => *address as u16,
XXXXERRORINSTRUCTION => 0x0000,
}
}
pub fn decode(input: u16, quirk_mode: &QuirkMode) -> Chip8CpuInstructions {
@ -661,7 +677,7 @@ impl Chip8CpuInstructions {
0x85 => Chip8CpuInstructions::LIDR(ubln),
_ => XXXXERRORINSTRUCTION,
},
_ => XXXXERRORINSTRUCTION,
_ => DW(addr_param),
}
}
pub fn execute(&self, input: &mut Chip8Computer) -> Chip8Computer {
@ -1084,6 +1100,7 @@ impl Chip8CpuInstructions {
input.registers.poke_i(offset + 1);
}
Chip8CpuInstructions::XXXXERRORINSTRUCTION => {}
// SCHIP1.1
Chip8CpuInstructions::SCD(x) => match input.quirk_mode {
QuirkMode::Chip8 => {
debug!("Attempt to execute SCD in Chip8 Mode");
@ -1092,6 +1109,7 @@ impl Chip8CpuInstructions {
input.video_memory.scroll_down(*x as i32);
}
},
// SCHIP 1.1
Chip8CpuInstructions::SCR => match input.quirk_mode {
QuirkMode::Chip8 => {
debug!("Attempt to execute SCR in Chip8 Mode");
@ -1100,6 +1118,7 @@ impl Chip8CpuInstructions {
input.video_memory.scroll_right();
}
},
// SCHIP 1.1
Chip8CpuInstructions::SCL => match input.quirk_mode {
QuirkMode::Chip8 => {
debug!("Attempt to execute SCL in Chip8 Mode");
@ -1108,6 +1127,7 @@ impl Chip8CpuInstructions {
input.video_memory.scroll_left();
}
},
// SCHIP 1.0
Chip8CpuInstructions::LOW => match input.quirk_mode {
QuirkMode::Chip8 => {
debug!("ATTEMPT TO SET LOWRES IN CHIP8MODE");
@ -1116,6 +1136,7 @@ impl Chip8CpuInstructions {
input.video_memory.set_lowres();
}
},
// SCHIP 1.0
Chip8CpuInstructions::HIGH => match input.quirk_mode {
QuirkMode::Chip8 => {
debug!("ATTEMPT TO SET HIGHRES IN CHIP8MODE");
@ -1171,6 +1192,9 @@ impl Chip8CpuInstructions {
}
}
}
DW(addr) => {
println!("DATA WORD FOUND...");
}
};
let cycle_time = Instant::now().duration_since(start_time).as_nanos();
// println!("\t\tTook {cycle_time}ms");

View File

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use crate::constants::CHIP8_KEYBOARD;
#[derive(Clone, Copy, Default, Serialize, Deserialize)]
#[derive(Clone, Copy, Default, Serialize, Deserialize, Debug)]
pub struct Keypad {
keys: [bool; 0x10],
}

View File

@ -1,9 +1,21 @@
use serde::{Deserialize, Serialize};
use std::fmt::Display;
#[derive(Default, Clone, Serialize, Deserialize)]
#[derive(Default, Clone, Serialize, Deserialize, Copy, Debug)]
pub enum QuirkMode {
#[default]
Chip8,
XOChip,
#[default]
SChipModern,
}
impl Display for QuirkMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let variant = match &self {
QuirkMode::Chip8 => "Chip8".to_string(),
QuirkMode::XOChip => "XO Chip".to_string(),
QuirkMode::SChipModern => "SChip-Modern".to_string(),
};
write!(f, "{}", variant)
}
}

View File

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
/// Registers. numbered 1-16 publicly.
/// Privately using zero base array so -1 to shift from pub to priv.
#[derive(Clone, Copy, Serialize, Deserialize)]
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
pub struct Chip8Registers {
pub registers: [u8; 16],
pub i_register: u16,

View File

@ -1,7 +1,7 @@
use log::trace;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Serialize, Deserialize)]
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
pub struct SoundTimer {
counter: i32,
}

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Serialize, Deserialize)]
#[derive(Clone, Default, Serialize, Deserialize, Debug)]
pub struct Chip8Stack {
items: Vec<u16>,
}

View File

@ -4,7 +4,10 @@ use crate::constants::*;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Serialize, Deserialize)]
use super::quirk_modes;
use super::quirk_modes::QuirkMode;
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Chip8SystemMemory {
memory: Vec<u8>,
}
@ -18,6 +21,7 @@ impl Default for Chip8SystemMemory {
x
}
}
impl Chip8SystemMemory {
fn empty_memory() -> Vec<u8> {
let mut working_memory = vec![];
@ -28,11 +32,20 @@ impl Chip8SystemMemory {
working_memory
}
pub fn reset(&mut self) {
pub fn reset(&mut self, quirk_modes: QuirkMode) {
self.memory = Chip8SystemMemory::empty_memory();
match quirk_modes {
QuirkMode::Chip8 => {
self.load_fonts_to_memory();
}
QuirkMode::XOChip => {
println!("NO XO FONT LOADING DONE YET");
}
QuirkMode::SChipModern => {
self.load_schip_fonts_to_memory();
}
}
}
pub fn new() -> Self {
Chip8SystemMemory {
@ -114,10 +127,10 @@ impl Chip8SystemMemory {
SCHIPFONT_F,
];
for (font_index, current_font) in all_font_characters.iter().enumerate() {
let base_offset = 0x100;
for font_mem_offset in 0..=4 {
let real_offset = base_offset + font_index * 0x10 + font_mem_offset;
self.poke(real_offset as u16, current_font[font_mem_offset]);
let base_offset = SCHIPFONT_OFFSET;
for font_mem_offset in 0..=9 {
let real_offset = base_offset + (font_index * 0x0a) as u32 + font_mem_offset;
self.poke(real_offset as u16, current_font[font_mem_offset as usize]);
}
}
}

View File

@ -6,13 +6,13 @@ use crate::constants::{
use log::debug;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Serialize, Deserialize)]
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
pub enum Chip8VideoModes {
LowRes,
HighRes,
}
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Chip8Video {
memory: Vec<bool>,
pub has_frame_changed: bool,
@ -172,14 +172,16 @@ impl Chip8Video {
}
pub fn scroll_left(&mut self) {
println!("SCROLLLEFTPRE:::[{}]", self.format_as_string());
let (width, height) = self.get_resolution();
for current_row in 0..height {
let row_offset = current_row * width;
for current_column in 0..width - 4 {
let source: usize = (row_offset + current_column) as usize;
let target: usize = source + 4;
for current_column in (0..width - 4) {
let target: usize = (row_offset + current_column) as usize;
let source: usize = target + 4;
self.memory[target] = self.memory[source];
println!("Moving from {source} to {target}");
}
let clear_start: usize = (row_offset + width - 4) as usize;
@ -187,6 +189,7 @@ impl Chip8Video {
self.memory[clear_start..clear_end].fill(false);
}
println!("SCROLLLEFTPOST:::[{}]", self.format_as_string());
}
pub fn scroll_up(&mut self, how_far: &u8) {

View File

@ -12,7 +12,7 @@ pub const CHIP8_KEYBOARD: [[u8; 4]; 4] = [
[0x01, 0x02, 0x03, 0x0C],
[0x04, 0x05, 0x06, 0x0D],
[0x07, 0x08, 0x09, 0x0E],
[0x0A, 0x00, 0x0B, 0x0F]
[0x0A, 0x00, 0x0B, 0x0F],
];
pub const SCHIP_VIDEO_HEIGHT: i32 = 64;
@ -65,6 +65,10 @@ pub const INST_LOW: &str = "LOW";
pub const INST_HIGH: &str = "HIGH";
pub const INST_ORY: &str = "ORY";
pub const INST_SCU: &str = "SCU";
/// Data Word
/// Data to be loaded to memory for application use
pub const INST_DW: &str = "DW";
pub const CHIP8_PROGRAM_LOAD_OFFSET: i32 = 0x200;
pub const CHIP8FONT_0: [u8; 5] = [0xF0, 0x90, 0x90, 0x90, 0xF0];
pub const CHIP8FONT_1: [u8; 5] = [0x20, 0x60, 0x20, 0x20, 0x70];
@ -83,90 +87,20 @@ pub const CHIP8FONT_D: [u8; 5] = [0xE0, 0x90, 0x90, 0x90, 0xE0];
pub const CHIP8FONT_E: [u8; 5] = [0xF0, 0x80, 0xF0, 0x80, 0xF0];
pub const CHIP8FONT_F: [u8; 5] = [0xF0, 0x80, 0xF0, 0x80, 0x80];
pub const SCHIPFONT_0: [u8; 0x20] = [
0x00, 0x00,
0x01, 0x80,
0x03, 0xc0,
0x06, 0x60,
0x0c, 0x30,
0x0c, 0x30,
0x18, 0x18,
0x18, 0x18,
0x18, 0x18,
0x18, 0x18,
0x0c, 0x30,
0x0c, 0x30,
0x06, 0x60,
0x03, 0xc0, // 0b0000001111000000
0x01, 0x80, // 0b0000000110000000
0x00, 0x00 // 0b0000000000000000
];
pub const SCHIPFONT_1: [u8; 0x20] = [
0x00, 0x00,
0x03, 0xc0,
0x02, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x03, 0xf0,
0x00, 0x00
];
pub const SCHIPFONT_2: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC7, 0xC3, 0xC0, 0xE0,
0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0xFF, 0xFF,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78];
pub const SCHIPFONT_3: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC7, 0xC3, 0xC0, 0xE0,
0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0xFF, 0xFF,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78];
pub const SCHIPFONT_4: [u8; 0x20] = [0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xF7, 0xF3, 0xF1,
0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78];
pub const SCHIPFONT_5: [u8; 0x20] = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x3F, 0x7F, 0x7F,
0x01, 0x01, 0xC1, 0xE3, 0xFF, 0xFE, 0xFC, 0x78,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78];
pub const SCHIPFONT_6: [u8; 0x20] = [0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
];
pub const SCHIPFONT_7: [u8; 0x20] = [0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0xF0, 0x78, 0x3C,
0x1E, 0x0F, 0x07, 0x03, 0x01, 0x01, 0x01, 0x01,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
];
pub const SCHIPFONT_8: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF,
0x7E, 0xFE, 0xC3, 0xC3, 0xFF, 0xFF, 0xFE, 0x7C,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
];
pub const SCHIPFONT_9: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF,
0xFF, 0x7F, 0x03, 0x03, 0xC7, 0xFF, 0xFE, 0x7C,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
];
pub const SCHIPFONT_A: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF,
0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
];
pub const SCHIPFONT_B: [u8; 0x20] = [0xFE, 0xFF, 0xFF, 0xFF, 0xC3, 0xC3, 0xFE, 0xFF,
0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xFE,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
];
pub const SCHIPFONT_C: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0x01, 0x01,
0x01, 0x01, 0xC3, 0xC3, 0xFF, 0xFE, 0xFC, 0x78,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
];
pub const SCHIPFONT_D: [u8; 0x20] = [0xFE, 0xFF, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3,
0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xFE, 0x7C, 0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
];
pub const SCHIPFONT_E: [u8; 0x20] = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, 0x03, 0xFF,
0xFF, 0xFF, 0x03, 0x03, 0xFF, 0xFF, 0xFF, 0xFF,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
];
pub const SCHIPFONT_F: [u8; 0x20] = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, 0x03, 0xFF,
0xFF, 0xFF, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78];
pub const SCHIPFONT_OFFSET: u32 = 0x81;
pub const SCHIPFONT_0: [u8; 0x0A] = [0xF0, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xF0];
pub const SCHIPFONT_1: [u8; 0x0A] = [0x20, 0x60, 0xA0, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70];
pub const SCHIPFONT_2: [u8; 0x0A] = [0xF0, 0x10, 0x10, 0x10, 0xF0, 0x80, 0x80, 0x80, 0x80, 0xF0];
pub const SCHIPFONT_3: [u8; 0x0A] = [0xF0, 0x10, 0x10, 0x10, 0xF0, 0x10, 0x10, 0x10, 0x10, 0xF0];
pub const SCHIPFONT_4: [u8; 0x0A] = [0x90, 0x90, 0x90, 0x90, 0xF0, 0x10, 0x10, 0x10, 0x10, 0x10];
pub const SCHIPFONT_5: [u8; 0x0A] = [0xF0, 0x80, 0x80, 0x80, 0xF0, 0x10, 0x10, 0x10, 0x10, 0xF0];
pub const SCHIPFONT_6: [u8; 0x0A] = [0xF0, 0x80, 0x80, 0x80, 0xF0, 0x90, 0x90, 0x90, 0x90, 0xF0];
pub const SCHIPFONT_7: [u8; 0x0A] = [0xF0, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10];
pub const SCHIPFONT_8: [u8; 0x0A] = [0xF0, 0x90, 0x90, 0x90, 0xF0, 0x90, 0x90, 0x90, 0x90, 0xF0];
pub const SCHIPFONT_9: [u8; 0x0A] = [0xF0, 0x90, 0x90, 0x90, 0xF0, 0x10, 0x10, 0x10, 0x10, 0xF0];
pub const SCHIPFONT_A: [u8; 0x0A] = [0xF0, 0x90, 0x90, 0x90, 0xF0, 0x90, 0x90, 0x90, 0x90, 0x90];
pub const SCHIPFONT_B: [u8; 0x0A] = [0xE0, 0x90, 0x90, 0x90, 0xE0, 0x90, 0x90, 0x90, 0x90, 0xE0];
pub const SCHIPFONT_C: [u8; 0x0A] = [0xF0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF0];
pub const SCHIPFONT_D: [u8; 0x0A] = [0xE0, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xE0];
pub const SCHIPFONT_E: [u8; 0x0A] = [0xF0, 0x80, 0x80, 0x80, 0xF0, 0x80, 0x80, 0x80, 0x80, 0xF0];
pub const SCHIPFONT_F: [u8; 0x0A] = [0xF0, 0x80, 0x80, 0x80, 0xF0, 0x80, 0x80, 0x80, 0x80, 0x80];

View File

@ -2,7 +2,9 @@ use gemma::chip8::computer::Chip8Computer;
use gemma::constants::CHIP8_VIDEO_MEMORY;
#[test]
fn smoke() { assert!(true) }
fn smoke() {
assert!(true)
}
fn load_result(to_load: &str) -> String {
std::fs::read_to_string(format!("../resources/test/{}", to_load)).unwrap()
@ -20,7 +22,7 @@ fn reset_clears_video() {
x.video_memory.poke(i as u16, i % 2 == 0);
}
x.reset();
x.reset(gemma::chip8::quirk_modes::QuirkMode::Chip8);
x.video_memory.reset();
assert_eq!(x.dump_video_to_string(), x.video_memory.format_as_string());
@ -40,7 +42,10 @@ fn level1_test() {
while x.num_cycles < 0x40 {
x.step_system();
}
assert_eq!(x.dump_video_to_string(), load_result("gemma_integration_level_1_test.asc"));
assert_eq!(
x.dump_video_to_string(),
load_result("gemma_integration_level_1_test.asc")
);
}
#[test]
@ -56,21 +61,25 @@ fn level2_test() {
// ...then verify that the current video memory of the chip-8
// simulator matches what we expect it to be
assert_eq!(x.dump_video_to_string(), load_result("gemma_integration_ibm_rom_output.asc"));
assert_eq!(
x.dump_video_to_string(),
load_result("gemma_integration_ibm_rom_output.asc")
);
}
#[test]
fn level3_test() {
let mut x = Chip8Computer::new();
x.load_bytes_to_memory(
0x200, (&load_rom("3-corax+.ch8"))
);
x.load_bytes_to_memory(0x200, (&load_rom("3-corax+.ch8")));
for i in 0..0x180 {
x.step_system();
}
assert_eq!(x.dump_video_to_string(), load_result("gemma_integration_corax_plus.asc"));
assert_eq!(
x.dump_video_to_string(),
load_result("gemma_integration_corax_plus.asc")
);
}
#[test]
@ -80,12 +89,18 @@ fn rps_test() {
for _ in 0..0xF0 {
x.step_system();
}
assert_eq!(x.dump_video_to_string(), load_result("gemma_integration_rps_stage1.asc"));
assert_eq!(
x.dump_video_to_string(),
load_result("gemma_integration_rps_stage1.asc")
);
x.keypad.push_key(0x01);
for _ in 0..0x200 {
x.step_system();
}
assert_eq!(x.dump_video_to_string(), load_result("gemma_integration_rps_stage2.asc"));
assert_eq!(
x.dump_video_to_string(),
load_result("gemma_integration_rps_stage2.asc")
);
}
#[test]
@ -97,5 +112,8 @@ fn level4_test() {
x.step_system();
}
assert_eq!(x.dump_video_to_string(), load_result("gemma_integration_flags.asc"));
assert_eq!(
x.dump_video_to_string(),
load_result("gemma_integration_flags.asc")
);
}

View File

@ -1,34 +1,55 @@
use flate2::write::{GzEncoder, GzDecoder};
use flate2::write::{GzDecoder, GzEncoder};
use flate2::Compression;
use std::io::prelude::*;
use gemma::chip8::computer::Chip8Computer;
use std::io::prelude::*;
fn load_result(to_load: &str) -> String {
std::fs::read_to_string(format!("resources/test/state/{}", to_load)).unwrap()
let full_path = format!("resources/test/state/{}", to_load);
println!("Loading state => (([{}]))", full_path);
std::fs::read_to_string(full_path).unwrap()
}
fn load_compressed_result(to_load: &str) -> String {
// load the file...
// ...then uncompress the string.
let mut decoder = GzDecoder::new(Vec::new());
decoder.write_all(load_result(to_load).as_ref()).expect("Decompression failed");
let decompressed_data = decoder.finish().expect("Failed to finish decompression");
String::from_utf8(decompressed_data).expect("Invalid UTF-8")
fn load_compressed_result(file_path: &str) -> io::Result<String> {
// Load the compressed file contents
let compressed_data = fs::read(file_path)?;
// Create a GzDecoder to uncompress the data
let mut decoder = GzDecoder::new(&compressed_data[..]);
let mut decompressed_data = String::new();
// Read the decompressed data directly into a String
decoder.read_to_string(&mut decompressed_data)?;
Ok(decompressed_data)
}
fn load_rom(to_load: &str) -> Vec<u8> {
std::fs::read(format!("resources/roms/{}", to_load)).unwrap()
std::fs::read(format!("resources/test/roms/{}", to_load)).unwrap()
}
#[test]
fn smoke_round_trip_serialize_deserialize() {
let x = Chip8Computer::new();
let expected_string = load_result("smoke_001_round_trip_serialize_deserialize.json");
let serialized = serde_json::to_string(&x).unwrap();
let deserialized: Chip8Computer = serde_json::from_str(&expected_string).unwrap();
println!("SERIALIZED [{}]", serialized);
// TODO: using trim here is a hack to handle editors adding a newline at the end of a file...even a 1 line file
assert_eq!(serialized.trim(), expected_string.trim());
assert_eq!(deserialized, x);
fn test_serialization_round_trip() {
let original_computer = Chip8Computer::new();
let expected_json = load_result("smoke_001_round_trip_serialize_deserialize.json");
// Serialize the Chip8Computer instance
let serialized = serde_json::to_string(&original_computer).expect("Serialization failed");
// Compare the serialized output to the expected JSON
println!("Serialized Output: [{}]", serialized);
assert_eq!(
serialized.trim(),
expected_json.trim(),
"Serialized output does not match expected JSON"
);
// Deserialize back to Chip8Computer and assert equality
let deserialized_computer: Chip8Computer =
serde_json::from_str(&serialized).expect("Deserialization failed");
assert_eq!(
deserialized_computer, original_computer,
"Deserialized instance does not match the original"
);
}
#[test]
@ -36,5 +57,7 @@ fn computer_001_system_zero_state() {
let x = Chip8Computer::new();
let expected_string = load_compressed_result("smoke_002_round_trip_serialize_deserialize.tflt");
let serialized = serde_json::to_string(&x).unwrap();
assert_eq!(serialized, expected_string);
}
}

View File

@ -4,7 +4,7 @@ use gemma::chip8::delay_timer::DelayTimer;
use gemma::chip8::instructions::Chip8CpuInstructions;
use gemma::chip8::instructions::Chip8CpuInstructions::JPX;
use gemma::chip8::keypad::Keypad;
use gemma::chip8::quirk_modes::QuirkMode::{Chip8, SChipModern, XOChip};
use gemma::chip8::quirk_modes::QuirkMode::{self, Chip8, SChipModern, XOChip};
use gemma::chip8::registers::Chip8Registers;
use gemma::chip8::sound_timer::SoundTimer;
use gemma::chip8::stack::Chip8Stack;
@ -14,6 +14,7 @@ use gemma::chip8::video::{Chip8Video, Chip8VideoModes};
use gemma::constants::*;
use log::debug;
use rand::random;
use serde::Serialize;
const TEST_OUTPUT_SAMPLE_DIR: &str = "../resources/test/";
@ -1497,3 +1498,26 @@ fn system_memory_new() {
let x = Chip8SystemMemory::new();
assert!(true);
}
#[test]
fn video_lowres_schip_draw_chip8_sprite() {
let mut x = Chip8Computer::new();
x.quirk_mode = QuirkMode::SChipModern;
x.video_memory.set_lowres();
// point at the 1 from chip8
x.registers.poke_i(0x0005);
// point to 1,2 for the drawing
x.registers.poke(0x01, 0x01);
x.registers.poke(0x02, 0x02);
Chip8CpuInstructions::DRW(0x01, 0x01, 0x08).execute(&mut x);
let expected_state = read_test_result("state/video_lowres_schip_draw_chip8_sprite_result.json");
let actual_state = serde_json::to_string(&x).unwrap();
assert_eq!(expected_state, actual_state);
}
#[test]
fn video_lowres_schip_draw_schip_sprite() {}
#[test]
fn video_highres_schip_draw_chip8_sprite() {}
#[test]
fn video_highres_schip_draw_schip_sprite() {}

View File

@ -1,18 +1,38 @@
use std::path::PathBuf;
use std::time::Instant;
use crate::support::gemma_egui_support::{EGuiFileList, GemmaEguiSupport};
use eframe::egui;
use egui::Key;
use gemma::chip8::computer::Chip8Computer;
use gemma::chip8::computer_manager::Chip8ComputerManager;
use std::path::PathBuf;
use std::time::Instant;
mod support;
const LIN_KEYS: [[(Key, u8); 4]; 4] = [
[(Key::Num1, 0x01), (Key::Num2, 0x02), (Key::Num3, 0x03), (Key::Num4, 0x0c)],
[(Key::Q, 0x04), (Key::W, 0x05), (Key::E, 0x06), (Key::R, 0x0d)],
[(Key::A, 0x07), (Key::S, 0x08), (Key::D, 0x09), (Key::F, 0x0e)],
[(Key::Z, 0x0a), (Key::X, 0x00), (Key::C, 0x0b), (Key::V, 0x0F)]
[
(Key::Num1, 0x01),
(Key::Num2, 0x02),
(Key::Num3, 0x03),
(Key::Num4, 0x0c),
],
[
(Key::Q, 0x04),
(Key::W, 0x05),
(Key::E, 0x06),
(Key::R, 0x0d),
],
[
(Key::A, 0x07),
(Key::S, 0x08),
(Key::D, 0x09),
(Key::F, 0x0e),
],
[
(Key::Z, 0x0a),
(Key::X, 0x00),
(Key::C, 0x0b),
(Key::V, 0x0F),
],
];
#[derive(Default)]
@ -88,17 +108,28 @@ fn main() -> eframe::Result {
// state.is_running = false;
}
if ui.button("Reset").clicked() {
computer.reset();
computer.reset(computer.quirks_mode());
// state.is_running = false;
}
});
if ui.button(format!("Load {}", state.selected_filename)).clicked() {
if ui
.button(format!("Load {}", state.selected_filename))
.clicked()
{
// println!("Should load the bin now");
let read_bin = std::fs::read(PathBuf::from(format!("resources/roms/{}", state.selected_filename))).unwrap();
let read_bin = std::fs::read(PathBuf::from(format!(
"resources/roms/{}",
state.selected_filename
)))
.unwrap();
computer.load_new_program_to_system_memory(read_bin);
};
EGuiFileList::display_path(PathBuf::from("resources/roms"), &mut state.selected_filename, ui);
EGuiFileList::display_path(
PathBuf::from("resources/roms"),
&mut state.selected_filename,
ui,
);
ctx.input(|input| {
// loop through the keys we are checking...
for row in LIN_KEYS {

View File

@ -1,10 +1,10 @@
use crate::Chip8Computer;
use egui::Rect;
use egui::Ui;
use egui::Vec2;
use egui::{Align, Color32, ComboBox, Pos2};
use std::fs::read_dir;
use std::path::PathBuf;
use egui::{Align, Color32, ComboBox, Pos2, TextBuffer};
use egui::Rect;
use egui::Vec2;
use egui::Ui;
use crate::Chip8Computer;
const CELL_WIDTH: f32 = 5.0;
const CELL_HEIGHT: f32 = 5.0;
@ -18,17 +18,22 @@ impl EGuiFileList {
ComboBox::from_label("Select ROM")
.selected_text(selected_filename.clone())
.show_ui(ui, |ui| {
let mut sorted_options = vec![];
for option in read_dir(root.as_path()).unwrap() {
let to_push = option.unwrap().file_name().into_string().unwrap_or( String::new());
let to_push = option
.unwrap()
.file_name()
.into_string()
.unwrap_or(String::new());
sorted_options.push(to_push);
}
sorted_options.sort();
for item in sorted_options {
// Add each option to the ComboBox
if ui.selectable_label(selected_filename.eq(&item.as_str()), item.clone()).clicked()
if ui
.selectable_label(selected_filename.eq(&item.as_str()), item.clone())
.clicked()
{
*selected_filename = item;
}
@ -43,7 +48,6 @@ pub struct GemmaEguiSupport {}
impl GemmaEguiSupport {
pub fn controls_view(system: &mut Chip8Computer, ui: &mut Ui) {
ui.with_layout(egui::Layout::left_to_right(Align::TOP), |ui| {
// ui.checkbox(&mut state.display_memory, "Display Memory");
// ui.checkbox(&mut state.display_video, "Display Video");
@ -52,7 +56,8 @@ impl GemmaEguiSupport {
}
pub fn registers_view(system: &Chip8Computer, ui: &mut Ui) {
ui.label(format!("V0-7: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} ",
ui.label(format!(
"V0-7: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} ",
system.registers.peek(0x00),
system.registers.peek(0x01),
system.registers.peek(0x02),
@ -62,7 +67,8 @@ impl GemmaEguiSupport {
system.registers.peek(0x06),
system.registers.peek(0x07)
));
ui.label(format!("V8-F: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} ",
ui.label(format!(
"V8-F: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} ",
system.registers.peek(0x08),
system.registers.peek(0x09),
system.registers.peek(0x0A),
@ -72,7 +78,11 @@ impl GemmaEguiSupport {
system.registers.peek(0x0E),
system.registers.peek(0x0F)
));
ui.label(format!("PC: {:04x}\tI: {:04x}", system.registers.peek_pc(), system.registers.peek_i()));
ui.label(format!(
"PC: {:04x}\tI: {:04x}",
system.registers.peek_pc(),
system.registers.peek_i()
));
}
pub fn video_view(system: &Chip8Computer, ui: &mut Ui) {

View File

@ -3,6 +3,7 @@ use gemma::chip8::computer_manager::Chip8ComputerManager;
use std::path::Path;
use std::time::Instant;
use std::{default::Default, path::PathBuf};
use support::ui_state::ROMPATH_DEFAULT;
use support::{emmagui_support::GemmaImguiSupport, ui_state::ImGuiUiState};
mod support;
@ -45,7 +46,12 @@ fn main() {
let mut system = Chip8ComputerManager::default();
println!("GOT A ROMS_DIRECTORY => [{:?}]", cli_options.roms_directory);
let mut ui_state = ImGuiUiState::new(cli_options.roms_directory);
println!("{:?}", std::env::var("PWD"));
let mut ui_state = if cli_options.roms_directory.is_none() {
ImGuiUiState::new(Some(PathBuf::from(ROMPATH_DEFAULT)))
} else {
ImGuiUiState::new(cli_options.roms_directory)
};
support::simple_init(file!(), move |_, ui| {
let current_time = Instant::now();

View File

@ -1,8 +1,10 @@
use crate::support::gui_file_list::GuiFileList;
use crate::ImGuiUiState;
use dimensioned::ucum::f32consts::CUP_M;
use gemma::chip8::computer::Chip8Computer;
use gemma::chip8::computer_manager::Chip8ComputerManager;
use gemma::chip8::computer_manager::ManagerDumpables::{Keyboard, Registers, Video};
use gemma::chip8::quirk_modes::QuirkMode::{self, *};
use gemma::chip8::system_memory::Chip8SystemMemory;
use gemma::constants::CHIP8_KEYBOARD;
use imgui::{CollapsingHeader, Condition, ImColor32, Ui};
@ -116,7 +118,7 @@ impl GemmaImguiSupport {
.build(|| {
/* System Step Counter */
ui.text(format!("Step {:04x}", system_to_control.num_cycles()).as_str());
ui.text(format!("Mode {}", system_to_control.quirks_mode()));
/* ROM Lister */
if CollapsingHeader::new("Roms").build(ui) {
let new_filename = GuiFileList::display_path(
@ -158,7 +160,7 @@ impl GemmaImguiSupport {
system_to_control.state().memory.peek(0x200),
system_to_control.state().memory.peek(0x201),
];
let mut show_buttons = bytes[0] != 0 || bytes[1] == 0xe0;
let show_buttons = bytes[0] != 0 || bytes[1] == 0xe0;
if show_buttons {
if ui.button("Step") {
@ -175,7 +177,7 @@ impl GemmaImguiSupport {
}
ui.same_line();
if ui.button("Reset") {
system_to_control.reset();
system_to_control.reset(system_to_control.quirks_mode());
}
if ui.button("Dump Video Memory") {
println!("{}", system_to_control.dump_to_string(Video));
@ -201,6 +203,27 @@ impl GemmaImguiSupport {
ui.input_int("Target IPS", &mut gui_state.target_ips)
.build();
};
let selectors = [Chip8, SChipModern, XOChip];
for current_selector in selectors {
let mut working_selector =
ui.selectable_config(current_selector.clone().to_string());
match system_to_control.quirks_mode() {
Chip8 => {
working_selector = working_selector.selected(true);
}
SChipModern => {
working_selector = working_selector.selected(true);
}
XOChip => {
working_selector = working_selector.selected(true);
}
}
if working_selector.build() {
system_to_control.reset(current_selector);
println!("CLICK ON {}", &current_selector);
}
}
});
}

View File

@ -16,7 +16,7 @@ pub struct ImGuiUiState {
pub frame_time: u32,
pub last_frame_instant: Instant,
pub target_ips: i32,
pub roms_root_path: PathBuf,
pub roms_root_path: PathBuf
}
impl Clone for ImGuiUiState {
@ -33,8 +33,7 @@ impl Clone for ImGuiUiState {
frame_time: self.frame_time,
last_frame_instant: self.last_frame_instant,
target_ips: self.target_ips,
roms_root_path: self.roms_root_path.to_owned(),
roms_root_path: self.roms_root_path.to_owned()
}
}
}
@ -53,7 +52,7 @@ impl Default for ImGuiUiState {
frame_time: 16,
last_frame_instant: Instant::now(),
target_ips: 200000,
roms_root_path: PathBuf::from(ROMPATH_DEFAULT),
roms_root_path: PathBuf::from(ROMPATH_DEFAULT)
}
}
}

View File

@ -1,13 +1,13 @@
mod support;
use std::{sync::Arc, time::Instant};
use crate::support::gemma_egui_support::GemmaEguiSupport;
use anyhow::Context;
use egui::TextBuffer;
use egui_glow::glow::{HasContext, COLOR_BUFFER_BIT};
use egui_sdl2_platform::sdl2;
use sdl2::event::{Event, WindowEvent};
use gemma::chip8::computer::Chip8Computer;
use sdl2::event::{Event, WindowEvent};
use std::{sync::Arc, time::Instant};
use support::timestep::TimeStep;
use crate::support::gemma_egui_support::GemmaEguiSupport;
const SCREEN_WIDTH: u32 = 800;
const SCREEN_HEIGHT: u32 = 480;
@ -15,14 +15,19 @@ const SCREEN_HEIGHT: u32 = 480;
async fn run() -> anyhow::Result<String> {
// Initialize SDL2 and video subsystem
let sdl = sdl2::init().map_err(|e| anyhow::anyhow!("Failed to create SDL context: {}", e))?;
let mut video = sdl.video().map_err(|e| anyhow::anyhow!("Failed to initialize SDL video subsystem: {}", e))?;
let mut video = sdl
.video()
.map_err(|e| anyhow::anyhow!("Failed to initialize SDL video subsystem: {}", e))?;
// Create SDL2 window and OpenGL context
let window = video.window("Window", SCREEN_WIDTH, SCREEN_HEIGHT)
let window = video
.window("Window", SCREEN_WIDTH, SCREEN_HEIGHT)
.opengl()
.position_centered()
.build()?;
let _gl_context = window.gl_create_context().expect("Failed to create GL context");
let _gl_context = window
.gl_create_context()
.expect("Failed to create GL context");
// Load OpenGL functions
let gl = unsafe {
@ -34,7 +39,9 @@ async fn run() -> anyhow::Result<String> {
// Setup Egui and SDL2 platform
let mut platform = egui_sdl2_platform::Platform::new(window.size())?;
let mut event_pump = sdl.event_pump().map_err(|e| anyhow::anyhow!("Failed to get SDL event pump: {}", e))?;
let mut event_pump = sdl
.event_pump()
.map_err(|e| anyhow::anyhow!("Failed to get SDL event pump: {}", e))?;
// Initial settings
let color = [0.0, 0.0, 0.0, 1.0]; // Background color
@ -75,7 +82,12 @@ async fn run() -> anyhow::Result<String> {
// Paint Egui outputs and update textures
let size = window.size();
painter.paint_and_update_textures([size.0, size.1], 1.0, paint_jobs.as_slice(), &full_output.textures_delta);
painter.paint_and_update_textures(
[size.0, size.1],
1.0,
paint_jobs.as_slice(),
&full_output.textures_delta,
);
window.gl_swap_window();
// Run the timestep logic
@ -85,38 +97,56 @@ async fn run() -> anyhow::Result<String> {
for event in event_pump.poll_iter() {
match event {
Event::Quit { .. }
| Event::KeyDown { keycode: Some(sdl2::keyboard::Keycode::Escape), .. } => break 'main,
Event::Window { window_id, win_event, .. } if window_id == window.id() => {
| Event::KeyDown {
keycode: Some(sdl2::keyboard::Keycode::Escape),
..
} => break 'main,
Event::Window {
window_id,
win_event,
..
} if window_id == window.id() => {
if let WindowEvent::Close = win_event {
break 'main;
}
}
Event::KeyUp { keycode: Some(sdl2::keyboard::Keycode::F3), .. } => {
Event::KeyUp {
keycode: Some(sdl2::keyboard::Keycode::F3),
..
} => {
println!("USER PRESSED F3 -> running");
is_running = true;
}
Event::KeyUp { keycode: Some(sdl2::keyboard::Keycode::F4), .. } => {
Event::KeyUp {
keycode: Some(sdl2::keyboard::Keycode::F4),
..
} => {
println!("USER PRESSED F4 -> stopping");
is_running = false;
}
Event::KeyDown { keycode: Some(sdl2::keyboard::Keycode::F5), .. } => {
Event::KeyDown {
keycode: Some(sdl2::keyboard::Keycode::F5),
..
} => {
println!("USER PRESSED F5 -> Step");
computer.step_system();
}
Event::KeyDown { keycode: Some(sdl2::keyboard::Keycode::F6), .. } => {
Event::KeyDown {
keycode: Some(sdl2::keyboard::Keycode::F6),
..
} => {
println!("USER PRESSED F6 -> RESET");
computer.reset();
computer.reset(computer.quirk_mode.clone());
}
Event::ControllerButtonDown { which, .. } => {
println!("PLAYER {which} DOWN");
}
Event::ControllerButtonDown { button, .. } => {
println!("BUTTON {:?}", button);
}
Event::JoyButtonDown {button_idx, .. } => {
Event::JoyButtonDown { button_idx, .. } => {
println!("JoyButtonDown {}", button_idx);
}
Event::JoyAxisMotion { which, axis_idx, .. } => {
Event::JoyAxisMotion {
which, axis_idx, ..
} => {
println!("JoyAxismotion {which} {axis_idx}");
}
_ => platform.handle_event(&event, &sdl, &video),

View File

@ -1,10 +1,10 @@
use crate::Chip8Computer;
use egui::Rect;
use egui::Ui;
use egui::Vec2;
use egui::{Align, Color32, ComboBox, Pos2};
use std::fs::read_dir;
use std::path::PathBuf;
use egui::{Align, Color32, ComboBox, Pos2, TextBuffer};
use egui::Rect;
use egui::Vec2;
use egui::Ui;
use crate::Chip8Computer;
const CELL_WIDTH: f32 = 5.0;
const CELL_HEIGHT: f32 = 5.0;
@ -18,17 +18,22 @@ impl EGuiFileList {
ComboBox::from_label("Select ROM")
.selected_text(selected_filename.clone())
.show_ui(ui, |ui| {
let mut sorted_options = vec![];
for option in read_dir(root.as_path()).unwrap() {
let to_push = option.unwrap().file_name().into_string().unwrap_or( String::new());
let to_push = option
.unwrap()
.file_name()
.into_string()
.unwrap_or(String::new());
sorted_options.push(to_push);
}
sorted_options.sort();
for item in sorted_options {
// Add each option to the ComboBox
if ui.selectable_label(selected_filename.eq(&item.as_str()), item.clone()).clicked()
if ui
.selectable_label(selected_filename.eq(&item.as_str()), item.clone())
.clicked()
{
*selected_filename = item;
}
@ -57,7 +62,7 @@ impl GemmaEguiSupport {
// state.is_running = false;
}
if ui.button("Reset").clicked() {
system.reset();
system.reset(system.quirk_mode.clone());
// state.is_running = false;
}
});
@ -71,8 +76,6 @@ impl GemmaEguiSupport {
// }
// EGuiFileList::display_path(PathBuf::from("resources/roms"), &mut state.selected_rom_filename, ui);
ui.with_layout(egui::Layout::left_to_right(Align::TOP), |ui| {
// ui.checkbox(&mut state.display_memory, "Display Memory");
// ui.checkbox(&mut state.display_video, "Display Video");
@ -81,7 +84,8 @@ impl GemmaEguiSupport {
}
pub fn registers_view(system: &Chip8Computer, ui: &mut Ui) {
ui.label(format!("V0-7: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} ",
ui.label(format!(
"V0-7: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} ",
system.registers.peek(0x00),
system.registers.peek(0x01),
system.registers.peek(0x02),
@ -91,7 +95,8 @@ impl GemmaEguiSupport {
system.registers.peek(0x06),
system.registers.peek(0x07)
));
ui.label(format!("V8-F: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} ",
ui.label(format!(
"V8-F: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} ",
system.registers.peek(0x08),
system.registers.peek(0x09),
system.registers.peek(0x0A),
@ -101,7 +106,11 @@ impl GemmaEguiSupport {
system.registers.peek(0x0E),
system.registers.peek(0x0F)
));
ui.label(format!("PC: {:04x}\tI: {:04x}", system.registers.peek_pc(), system.registers.peek_i()));
ui.label(format!(
"PC: {:04x}\tI: {:04x}",
system.registers.peek_pc(),
system.registers.peek_i()
));
}
pub fn video_view(system: &Chip8Computer, ui: &mut Ui) {

View File

@ -1,9 +1,14 @@
use std::fs;
use gemma::chip8::util::InstructionUtil;
use gemma::chip8::{instructions::Chip8CpuInstructions, quirk_modes::QuirkMode};
use std::fs::{self, File};
use std::io::Write;
// Ch8Asm
// Converts well formed CH8ASM.
// no variables.
// no labels.
// nothing fun.
// VALID FORMAT
// <INSTRUCTION> <0x|0X><parameter>[, ][<0x|OX><parameter>[, ][<0x|OX><parameter>]][; comment]
use pest::Parser;
use pest_derive::Parser;
@ -14,62 +19,30 @@ pub struct Chip8AsmParser;
fn main() {
println!("Taxation is Theft");
let unparsed = fs::read_to_string("resources/test/gemma_disassembler_1_chip_logo_ch8_asm.asc").expect("Unable to read input");
let unparsed = fs::read_to_string("resources/test/gemma_disassembler_manual_document.asc")
.expect("Unable to read input");
let file = Chip8AsmParser::parse(Rule::file, &unparsed).expect("Unable to parse. Try again.")
.next().unwrap();
let file = Chip8AsmParser::parse(Rule::file, &unparsed)
.expect("Unable to parse. Try again.")
.next()
.unwrap();
println!("PREPARING TO WRITE THE TARGET FILE...");
let mut target_file = File::create("thisismytarget.ch8").expect("Unable to create target");
for record in file.into_inner() {
// let mut working_instruction = Chip8CpuInstructions::XXXXERRORINSTRUCTION;
match record.as_rule() {
Rule::instruction => {
println!("INSTRUCTION = {:?}", record.into_inner().flatten())
}
_ => {
println!("UNHANDLED PART.");
Rule::record => {
print!("record = {:?}\t", record.as_str());
let x = Chip8CpuInstructions::from_str(record.as_str());
println!("DECODED TO {:?} {:04x}", x, x.encode());
let (high, low) = InstructionUtil::split_bytes(x.encode());
target_file
.write_all(&[high, low])
.expect("Unable to write to the file.");
}
_ => {}
}
}
}
#[cfg(test)]
mod test {
use pest::Parser;
use crate::{Chip8AsmParser, Rule};
#[test]
fn bits_all_parse() {
println!("PARSED: {:?}",
Chip8AsmParser::parse(Rule::instruction, "CLS")
);
println!("PARSED: {:?}",
Chip8AsmParser::parse(Rule::parameter, "0x01")
);
let parsed = Chip8AsmParser::parse(Rule::comment, "; comment").unwrap();
for i in parsed {
println!("PARSED COMMENT -> {:?}", i);
}
let parsed =
Chip8AsmParser::parse(Rule::record, "CLS ; comment").unwrap();
for i in parsed {
println!("RULE PAIR THING: {:?}", i);
}
let parsed = Chip8AsmParser::parse(Rule::record, "ADDI 0x01 ; comment");
for i in parsed {
println!("RULE PAIR THING: {:?}", i);
}
println!("PARSED: {:?}",
Chip8AsmParser::parse(Rule::record, "ADD ADD ADD")
);
println!("PARSED: {:?}",
Chip8AsmParser::parse(Rule::record, "ADD 0x01 0x02 ; Comment")
);
println!("PARSED: {:?}",
Chip8AsmParser::parse(Rule::record, "ADD 0x01 0x02 ; Comment")
);
}
}

View File

@ -1,7 +1,7 @@
WHITESPACE = _{ " " }
instruction = { ASCII_ALPHA+ }
parameter = { "0X" ~ ASCII_HEX_DIGIT+ }
comment = { ";" ~ WHITESPACE* ~ ASCII* }
parameters = { parameter ~ (WHITESPACE* ~ "," ~ WHITESPACE* ~ parameter)* }
record = { instruction ~ WHITESPACE* ~ parameters? ~ WHITESPACE* ~ comment? }
file = { SOI ~ (record ~ ("\r\n" | "\n"))* ~ EOI }
WHITESPACE = _{ " " | "\t" } // Allow tabs and multiple spaces
instruction = { ASCII_ALPHA+ } // Instruction, e.g., "CLS" or "LDX"
parameter = { ("0x" | "0X") ~ ASCII_HEX_DIGIT+ } // Parameter format, e.g., "0X250"
comment = { ";" ~ (!"\n" ~ ANY)* } // Capture everything after ";", up to end of line
parameters = { (parameter ~ (WHITESPACE* ~ ","* ~ WHITESPACE* ~ parameter)*)?} // Zero or more parameters
record = { instruction ~ WHITESPACE* ~ parameters ~ WHITESPACE* ~ comment? } // Record structure
file = { SOI ~ (record ~ (("\r\n" | "\n") | WHITESPACE*))* ~ EOI } // Allow for trailing newlines and empty lines

Binary file not shown.

Binary file not shown.

BIN
resources/roms/000test1.ch8 Normal file

Binary file not shown.

View File

@ -29,7 +29,7 @@ LDX 0X00, 0X18 ; 6018
LDI 0X2C8 ; A2C8
DRW 0X00, 0X01, 0X0F ; D01F
LDX 0X00, 0X20 ; 6020
LDI 2D7 ; A2D7
LDI 0X2D7 ; A2D7
DRW 0X00, 0X01, 0X0F ; D01F
LDX 0X00, 0X28 ; 6028
LDI 0X2E6 ; A2E6

View File

@ -23,11 +23,11 @@ JPI 0x0123 ; Bytes [0xb123] offset [0x002a]
RND 0x01, 0x23 ; Bytes [0xc123] offset [0x002c]
DRW 0x01, 0x02, 0x03 ; Bytes [0xd123] offset [0x002e]
SKP 0x01 ; Bytes [0xe19e] offset [0x0030]
SNKP ; Bytes [0xe1a1] offset [0x0032]
SKNP 0x01 ; Bytes [0xe1a1] offset [0x0032]
LDRD 0x01 ; Bytes [0xf107] offset [0x0034]
LDRK 0x01 ; Bytes [0xf10a] offset [0x0036]
LDD 0x01 ; Bytes [0xf115] offset [0x0038]
LIDS 0x01 ; Bytes [0xf118] offset [0x003a]
LDIS 0x01 ; Bytes [0xf118] offset [0x003a]
ADDI 0x01 ; Bytes [0xf11e] offset [0x003c]
LDF 0x01 ; Bytes [0xf129] offset [0x003e]
BCD 0x01 ; Bytes [0xf133] offset [0x0040]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resources/test/roms/RPS.ch8 Normal file

Binary file not shown.