Compare commits

..

10 Commits

Author SHA1 Message Date
ec9b7c4825 add: registers
Some checks failed
Rust / build (push) Has been cancelled
2025-02-20 17:08:30 +00:00
5a457da578 bump: version 2025-02-20 08:23:09 +00:00
2bb85f7a0b update: dependency version simple logger 2025-02-20 08:13:58 +00:00
ced440a302 update: dependencies 2025-02-20 08:12:54 +00:00
9eea36efa5 add: more helpful warning msgs 2025-02-20 08:10:40 +00:00
867774e367 fix: default configuration 2025-02-20 08:01:53 +00:00
df75a881ac add: configuration for throttling 2025-02-20 07:56:25 +00:00
d6e813b927 fix: get key resource 2025-02-19 16:59:52 +00:00
9b39560d43 [fix] keyboard bug 2025-02-16 12:09:17 +00:00
f56426a9ec Update rust.yml 2025-02-14 22:51:29 +00:00
11 changed files with 281 additions and 112 deletions

View File

@@ -29,7 +29,7 @@ jobs:
uses: actions/upload-artifact@v4.3.1 uses: actions/upload-artifact@v4.3.1
if: ${{ github.event_name == 'push' }} if: ${{ github.event_name == 'push' }}
with: with:
name: byte-pusher-emu name: porcel8
path: target/release/byte-pusher-emu path: target/release/porcel8
if-no-files-found: error if-no-files-found: error
overwrite: false overwrite: false

110
Cargo.lock generated
View File

@@ -18,9 +18,9 @@ dependencies = [
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.6" version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
@@ -56,6 +56,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.5.0" version = "1.5.0"
@@ -70,9 +76,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.1" version = "4.5.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -80,9 +86,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.1" version = "4.5.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -92,9 +98,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.0" version = "4.5.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@@ -104,9 +110,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.7.0" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
@@ -135,20 +141,21 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.12" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi", "wasi",
"windows-targets 0.52.4",
] ]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.4.1" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "itoa" name = "itoa"
@@ -164,15 +171,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.153" version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.21" version = "0.4.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
[[package]] [[package]]
name = "num-conv" name = "num-conv"
@@ -191,7 +198,7 @@ dependencies = [
[[package]] [[package]]
name = "porcel8" name = "porcel8"
version = "0.2.1" version = "0.3.1"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"clap", "clap",
@@ -233,20 +240,20 @@ dependencies = [
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
dependencies = [ dependencies = [
"libc",
"rand_chacha", "rand_chacha",
"rand_core", "rand_core",
"zerocopy",
] ]
[[package]] [[package]]
name = "rand_chacha" name = "rand_chacha"
version = "0.3.1" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core", "rand_core",
@@ -254,20 +261,21 @@ dependencies = [
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.6.4" version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "a88e0da7a2c97baa202165137c158d0a2e824ac465d13d81046727b34cb247d3"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"zerocopy",
] ]
[[package]] [[package]]
name = "sdl2" name = "sdl2"
version = "0.36.0" version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8356b2697d1ead5a34f40bcc3c5d3620205fe0c7be0a14656223bfeec0258891" checksum = "3b498da7d14d1ad6c839729bd4ad6fc11d90a57583605f3b4df2cd709a9cd380"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"lazy_static", "lazy_static",
"libc", "libc",
"sdl2-sys", "sdl2-sys",
@@ -275,9 +283,9 @@ dependencies = [
[[package]] [[package]]
name = "sdl2-sys" name = "sdl2-sys"
version = "0.36.0" version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26bcacfdd45d539fb5785049feb0038a63931aa896c7763a2a12e125ec58bd29" checksum = "951deab27af08ed9c6068b7b0d05a93c91f0a8eb16b6b816a5e73452a43521d3"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@@ -306,9 +314,9 @@ dependencies = [
[[package]] [[package]]
name = "simple_logger" name = "simple_logger"
version = "4.3.3" version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e7e46c8c90251d47d08b28b8a419ffb4aede0f87c2eea95e17d1d5bacbf3ef1" checksum = "e8c5dfa5e08767553704aa0ffd9d9794d527103c736aba9854773851fd7497eb"
dependencies = [ dependencies = [
"colored", "colored",
"log", "log",
@@ -386,9 +394,12 @@ checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
"wit-bindgen-rt",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
@@ -521,3 +532,32 @@ name = "windows_x86_64_msvc"
version = "0.52.4" version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
"bitflags 2.8.0",
]
[[package]]
name = "zerocopy"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View File

@@ -1,17 +1,17 @@
[package] [package]
name = "porcel8" name = "porcel8"
version = "0.2.1" version = "0.3.1"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
clap = { version = "4.5", features = ["derive"] } clap = { version = "4.5.30", features = ["derive"] }
log = "0.4.20" log = "0.4.25"
simple_logger = "4.3" simple_logger = "5.0.0"
byteorder = "1.5" byteorder = "1.5"
sdl2 = "0.36.0" sdl2 = "0.37.0"
rand = "0.8.5" rand = "0.9.0"
[profile.release] [profile.release]
strip = true strip = true

View File

@@ -13,6 +13,7 @@ pub struct Porcel8ProgramArgs {
help = "Emulate new behaviour of instructions (As seen in Chip-48 and SuperChip8)", help = "Emulate new behaviour of instructions (As seen in Chip-48 and SuperChip8)",
default_value_t = true default_value_t = true
)] )]
/// Use updated CHIP-8 behaviours.
pub new_chip8_behaviour: bool, pub new_chip8_behaviour: bool,
#[arg( #[arg(
short='i', short='i',
@@ -20,5 +21,12 @@ pub struct Porcel8ProgramArgs {
help = "Halt on invalid instruction", help = "Halt on invalid instruction",
default_value_t = false default_value_t = false
)] )]
/// Halt on finding invalid instruction
pub halt_on_invalid: bool, pub halt_on_invalid: bool,
/// Enable Instruction Throttling
#[arg(short='t', default_value_t=true)]
pub do_instruction_throttling: bool,
/// Target Instructions per second, if throttling is enabled
#[arg(short='r',long,default_value_t=750u64)]
pub ips_throttling_rate: u64
} }

View File

@@ -1,21 +1,22 @@
use crate::{device::instruction::Instruction, util::EmulatorError}; use crate::{device::instruction::Instruction, util::EmulatorError};
use crate::device::keyboard::Keyboard; use crate::device::keyboard::Keyboard;
use crate::device::timer::DeviceTimerManager; use crate::device::timer::DeviceTimerManager;
use crate::util::EmulatorResult; use crate::util::{DeviceConfig, EmulatorResult};
use rand::random; use rand::random;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::thread::sleep; use std::thread::sleep;
use std::time::Duration; use std::time::Duration;
use super::registers::RegisterFile;
pub struct Device { pub struct Device {
pub registers: RegisterFile, pub registers: RegisterFile,
pub memory: Box<[u8; Self::DEVICE_MEMORY_SIZE]>, pub memory: Box<[u8; Self::DEVICE_MEMORY_SIZE]>,
pub timer: DeviceTimerManager, pub timer: DeviceTimerManager,
pub stack: Vec<u16>, pub stack: Vec<u16>,
pub frame_buffer: Arc<Mutex<Box<[bool; 64 * 32]>>>, pub frame_buffer: Arc<Mutex<Box<[bool; 64 * 32]>>>,
pub new_chip8_mode: bool,
pub halt_on_invalid: bool,
pub device_keyboard: Keyboard, pub device_keyboard: Keyboard,
pub device_config: DeviceConfig
} }
impl Device { impl Device {
@@ -27,8 +28,7 @@ impl Device {
timer: DeviceTimerManager, timer: DeviceTimerManager,
fb: Arc<Mutex<Box<[bool; Device::FRAME_BUFFER_SIZE]>>>, fb: Arc<Mutex<Box<[bool; Device::FRAME_BUFFER_SIZE]>>>,
device_keyboard: Keyboard, device_keyboard: Keyboard,
new_chip8_mode: bool, device_config: DeviceConfig
halt_on_invalid: bool
) -> Device { ) -> Device {
let memory = vec![0u8; Self::DEVICE_MEMORY_SIZE] let memory = vec![0u8; Self::DEVICE_MEMORY_SIZE]
.into_boxed_slice() .into_boxed_slice()
@@ -36,43 +36,37 @@ impl Device {
.unwrap(); .unwrap();
log::trace!("Successfully initiated device memory"); log::trace!("Successfully initiated device memory");
Device { Device {
registers: RegisterFile::new(), registers: RegisterFile::default(),
memory, memory,
frame_buffer: fb, frame_buffer: fb,
stack: Vec::with_capacity(16), stack: Vec::with_capacity(16),
halt_on_invalid,
timer, timer,
new_chip8_mode,
device_keyboard, device_keyboard,
device_config
} }
} }
} }
impl Device { impl Device {
pub const ROM_START: usize = 0x200;
const FONT_HEIGHT: u16 = 5; const FONT_HEIGHT: u16 = 5;
const FONT_DEFAULT_MEM_LOCATION_START: usize = 0x50; const FONT_DEFAULT_MEM_LOCATION_START: usize = 0x50;
const FONT_DEFAULT_MEM_LOCATION_END: usize = 0x9F; const FONT_DEFAULT_MEM_LOCATION_END: usize = 0x9F;
const ROM_START: usize = 0x200;
// Throttling configuration for cpu
const DO_CHIP_CPU_THROTTLING:bool = true;
const TARGET_CPU_SPEED_INSTRUCTIONS_PER_SECOND: u64 = 800;
const TARGET_CPU_INSTRUCTION_TIME: Duration = Duration::from_micros(1_000_000/Self::TARGET_CPU_SPEED_INSTRUCTIONS_PER_SECOND);
pub fn cycle(&mut self) -> EmulatorResult<()> { pub fn cycle(&mut self) -> EmulatorResult<()> {
let time_start = std::time::Instant::now(); let time_start = std::time::Instant::now();
self.device_keyboard.update_keyboard_registers()?; self.device_keyboard.update_keyboard_registers()?;
let pc = self.registers.pc as usize; let pc = self.registers.pc as usize;
let instr_slice = self.memory.get(pc..pc + 2).expect("Failed to get memory"); let instr_slice = self.memory.get(pc..pc + 2).expect(format!("Failed to get memory at {}",pc).as_str());
self.registers.pc += 2; self.registers.pc += 2;
let instruction = Instruction::decode_instruction(instr_slice); let instruction = Instruction::decode_instruction(instr_slice);
self.execute_instruction(instruction)?; self.execute_instruction(instruction)?;
if let Some(throttling_duration) = self.device_config.get_throttling_config() {
let instruction_time = time_start.elapsed(); let instruction_time = time_start.elapsed();
let time_left_to_sleep_for_instruction = Self::TARGET_CPU_INSTRUCTION_TIME.checked_sub(instruction_time).unwrap_or(Duration::ZERO); let time_left_to_sleep_for_instruction = throttling_duration.checked_sub(instruction_time).unwrap_or(Duration::ZERO);
if Self::DO_CHIP_CPU_THROTTLING {
log::trace!("Instruction took {:?}, left with {:?}",instruction_time,time_left_to_sleep_for_instruction); log::trace!("Instruction took {:?}, left with {:?}",instruction_time,time_left_to_sleep_for_instruction);
sleep(time_left_to_sleep_for_instruction); sleep(time_left_to_sleep_for_instruction);
} }
@@ -84,12 +78,11 @@ impl Device {
y * Self::FRAME_BUFFER_WIDTH + x y * Self::FRAME_BUFFER_WIDTH + x
} }
pub fn execute_instruction(&mut self, instruction: Instruction) -> EmulatorResult<()> { pub fn execute_instruction(&mut self, instruction: Instruction) -> EmulatorResult<()> {
// thread::sleep(Duration::from_millis(250));
log::trace!("Executing {:?}, {:?}", &instruction, &self.registers); log::trace!("Executing {:?}, {:?}", &instruction, &self.registers);
match instruction { match instruction {
Instruction::InvalidInstruction => { Instruction::InvalidInstruction => {
log::info!("Executing passthrough"); log::info!("Executing passthrough");
if self.halt_on_invalid { if self.device_config.should_halt_on_invalid() {
return Err(EmulatorError::IOError("Caught Invalid Instruction".to_string())); return Err(EmulatorError::IOError("Caught Invalid Instruction".to_string()));
} }
}, },
@@ -152,7 +145,7 @@ impl Device {
} }
} }
Instruction::JumpWithOffset(x, num) => { Instruction::JumpWithOffset(x, num) => {
let regnum = if self.new_chip8_mode { x } else { 0 }; let regnum = if self.device_config.is_new_chip8() { x } else { 0 };
let new_pc = self.registers.v[regnum] as u16 + num; let new_pc = self.registers.v[regnum] as u16 + num;
self.registers.pc = new_pc; self.registers.pc = new_pc;
} }
@@ -205,7 +198,7 @@ impl Device {
self.set_flag_register(!is_overflow); self.set_flag_register(!is_overflow);
} }
Instruction::RShift(x, y) => { Instruction::RShift(x, y) => {
if !self.new_chip8_mode { if !self.device_config.is_new_chip8() {
self.registers.v[x] = self.registers.v[y]; self.registers.v[x] = self.registers.v[y];
} }
let val = self.registers.v[x]; let val = self.registers.v[x];
@@ -214,7 +207,7 @@ impl Device {
self.set_flag_register(bit_carry); self.set_flag_register(bit_carry);
} }
Instruction::LShift(x, y) => { Instruction::LShift(x, y) => {
if !self.new_chip8_mode { if !self.device_config.is_new_chip8() {
self.registers.v[x] = self.registers.v[y]; self.registers.v[x] = self.registers.v[y];
} }
let left = self.registers.v[x]; let left = self.registers.v[x];
@@ -239,7 +232,7 @@ impl Device {
let reg_value = self.registers.v[x]; let reg_value = self.registers.v[x];
let index_original = self.registers.i; let index_original = self.registers.i;
// newer instruction set requires wrapping on 12 bit overflow, and setting vf // newer instruction set requires wrapping on 12 bit overflow, and setting vf
let addn_res = if self.new_chip8_mode { let addn_res = if self.device_config.is_new_chip8() {
let overflowing = (reg_value as u16 + index_original) >= 0x1000; let overflowing = (reg_value as u16 + index_original) >= 0x1000;
self.set_flag_register(overflowing); self.set_flag_register(overflowing);
(reg_value as u16 + index_original) % 0x1000 (reg_value as u16 + index_original) % 0x1000
@@ -249,8 +242,13 @@ impl Device {
self.registers.i = addn_res; self.registers.i = addn_res;
} }
Instruction::GetKey(x) => { Instruction::GetKey(x) => {
let key_expected = self.registers.v[x]; let mut possible_presses = (0..=0xfu8).filter(|x|{self.device_keyboard.query_key_down(*x)});
if !self.device_keyboard.query_key_down(key_expected) { let pressed = possible_presses.next();
if let Some(pressed_key) = pressed {
self.registers.v[x] = pressed_key;
} else{
self.registers.pc -= 2; self.registers.pc -= 2;
} }
} }
@@ -281,7 +279,7 @@ impl Device {
let index = self.registers.i as usize; let index = self.registers.i as usize;
self.memory[index..=(index + last_reg_to_store)].copy_from_slice(reg_slice); self.memory[index..=(index + last_reg_to_store)].copy_from_slice(reg_slice);
// Old Chip8 used to use i as a incrementing index // Old Chip8 used to use i as a incrementing index
if !self.new_chip8_mode { if !self.device_config.is_new_chip8() {
self.registers.i += last_reg_to_store as u16 + 1; self.registers.i += last_reg_to_store as u16 + 1;
} }
} }
@@ -290,7 +288,7 @@ impl Device {
let mem_slice = &self.memory[index..=(index + last_reg_to_load)]; let mem_slice = &self.memory[index..=(index + last_reg_to_load)];
self.registers.v[0..=last_reg_to_load].copy_from_slice(mem_slice); self.registers.v[0..=last_reg_to_load].copy_from_slice(mem_slice);
// Old Chip8 used to use i as a incrementing index // Old Chip8 used to use i as a incrementing index
if !self.new_chip8_mode { if !self.device_config.is_new_chip8() {
self.registers.i += last_reg_to_load as u16 + 1; self.registers.i += last_reg_to_load as u16 + 1;
} }
} }
@@ -382,23 +380,3 @@ impl Drop for Device {
self.timer.send_stop_signal() self.timer.send_stop_signal()
} }
} }
#[derive(Debug)]
pub struct RegisterFile {
pub v: [u8; 0x10],
/// program counter - only u12 technically.
pub pc: u16,
/// stack pointer
pub i: u16,
}
impl RegisterFile {
pub const DEFAULT_PC_VALUE: u16 = Device::ROM_START as u16;
pub fn new() -> RegisterFile {
RegisterFile {
v: [0; 0x10],
pc: Self::DEFAULT_PC_VALUE,
i: 0,
}
}
}

View File

@@ -186,8 +186,9 @@ impl Instruction {
let x = (instruction & 0xf00) >> 8; let x = (instruction & 0xf00) >> 8;
Instruction::LoadRegistersFromMemory(x as usize) Instruction::LoadRegistersFromMemory(x as usize)
} }
_ => { instruction_nibble => {
todo!("Unimplemented instruction") log::error!("Unimplemented instruction with nibble {:?}",instruction_nibble);
Instruction::InvalidInstruction
} }
} }
} }

View File

@@ -50,7 +50,7 @@ impl Keyboard {
/// Query if key is down /// Query if key is down
pub fn query_key_down(&self, key_num: u8) -> bool { pub fn query_key_down(&self, key_num: u8) -> bool {
(self.bitflags | 1 << key_num) == (1 << key_num) (self.bitflags & (1 << key_num)) == (1 << key_num)
} }
fn update_keyboard_state(&mut self, keyboard_event: KeyboardEvent) { fn update_keyboard_state(&mut self, keyboard_event: KeyboardEvent) {
@@ -67,11 +67,53 @@ impl Keyboard {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Key; use super::{Key, Keyboard};
#[test] #[test]
fn test_key_assignment(){ fn test_key_assignment(){
assert_eq!(0,Key::K0 as u16); assert_eq!(0,Key::K0 as u16);
assert_eq!(15,Key::KF as u16); assert_eq!(15,Key::KF as u16);
} }
#[test]
fn test_key_at_nothing_pressed(){
let (_sender,receiver) = std::sync::mpsc::sync_channel(1);
let keyboard = Keyboard::new(receiver);
assert_no_key_pressed(&keyboard);
}
#[test]
fn test_key_down_then_up(){
let (sender,receiver) = std::sync::mpsc::sync_channel(1);
let mut keyboard = Keyboard::new(receiver);
assert_no_key_pressed(&keyboard);
sender.try_send(super::KeyboardEvent::KeyDown(Key::K0)).expect("Could not send");
keyboard.update_keyboard_registers().expect("Could not update keyboard");
assert_eq!(1,keyboard.bitflags);
assert_eq!(true,keyboard.query_key_down(0));
for i in 1..=0xF {
assert_eq!(false,keyboard.query_key_down(i));
}
sender.try_send(super::KeyboardEvent::KeyUp(Key::K0)).expect("Could not send");
keyboard.update_keyboard_registers().expect("Could not update keyboard");
assert_no_key_pressed(&keyboard);
}
fn assert_no_key_pressed(keyboard: &Keyboard){
assert_eq!(0,keyboard.bitflags);
for i in 0..=0xF {
assert_eq!(false,keyboard.query_key_down(i),"Failed to match at index {}, bitflags at {}",i,keyboard.bitflags);
}
}
} }

View File

@@ -2,5 +2,6 @@ pub mod timer;
pub mod keyboard; pub mod keyboard;
pub mod instruction; pub mod instruction;
mod device; mod device;
mod registers;
pub use device::*; pub use device::*;

22
src/device/registers.rs Normal file
View File

@@ -0,0 +1,22 @@
use super::Device;
#[derive(Debug)]
pub struct RegisterFile {
pub v: [u8; 0x10],
/// program counter - only u12 technically.
pub pc: u16,
/// stack pointer
pub i: u16,
}
impl RegisterFile {
pub const DEFAULT_PC_VALUE: u16 = Device::ROM_START as u16;
}
impl Default for RegisterFile{
fn default() -> Self {
Self { v: [0;0x10], pc: Self::DEFAULT_PC_VALUE, i: 0 }
}
}

View File

@@ -2,7 +2,6 @@ use std::sync::{Arc, Mutex};
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use std::thread; use std::thread;
use std::thread::JoinHandle; use std::thread::JoinHandle;
use std::time::Duration;
use clap::Parser; use clap::Parser;
use log::LevelFilter; use log::LevelFilter;
use sdl2::audio::{AudioQueue, AudioSpecDesired}; use sdl2::audio::{AudioQueue, AudioSpecDesired};
@@ -14,6 +13,7 @@ use sdl2::render::BlendMode;
use sdl2::render::WindowCanvas; use sdl2::render::WindowCanvas;
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use util::DeviceConfig;
use crate::args::Porcel8ProgramArgs; use crate::args::Porcel8ProgramArgs;
use crate::device::Device; use crate::device::Device;
@@ -33,7 +33,7 @@ const WINDOW_TITLE: &str = "porcel8";
fn main() -> EmulatorResult<()> { fn main() -> EmulatorResult<()> {
SimpleLogger::new().with_level(LevelFilter::Info).env().init().unwrap(); SimpleLogger::new().with_level(LevelFilter::Info).env().init().unwrap();
let Porcel8ProgramArgs { filename, new_chip8_behaviour, draw_scale, halt_on_invalid } = Porcel8ProgramArgs::parse(); let Porcel8ProgramArgs { filename, new_chip8_behaviour, draw_scale, halt_on_invalid, do_instruction_throttling, ips_throttling_rate: ipms_throttling_rate } = Porcel8ProgramArgs::parse();
log::info!("Started emulator"); log::info!("Started emulator");
@@ -45,8 +45,8 @@ fn main() -> EmulatorResult<()> {
let (sdl_kb_adapter, device_keyboard) = SdlKeyboardAdapter::new_keyboard(); let (sdl_kb_adapter, device_keyboard) = SdlKeyboardAdapter::new_keyboard();
timer.start(); timer.start();
let device_config = DeviceConfig::new(new_chip8_behaviour, halt_on_invalid, do_instruction_throttling, ipms_throttling_rate);
let device = Device::new(timer, frame_buffer_for_device, device_keyboard, new_chip8_behaviour, halt_on_invalid); let device = Device::new(timer, frame_buffer_for_device, device_keyboard, device_config);
let (device_termination_signal_sender, compute_handle) = start_compute_thread(filename, device)?; let (device_termination_signal_sender, compute_handle) = start_compute_thread(filename, device)?;
@@ -65,7 +65,7 @@ fn main() -> EmulatorResult<()> {
match event { match event {
Event::Quit { .. } | Event::Quit { .. } |
Event::KeyDown { keycode: Some(Keycode::Escape), .. } => { Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
device_termination_signal_sender.send(()).expect("Could not send"); device_termination_signal_sender.send(())?;
break 'running; break 'running;
} }
Event::KeyDown { keycode: Some(keycode), repeat: false, .. } => { Event::KeyDown { keycode: Some(keycode), repeat: false, .. } => {
@@ -111,8 +111,6 @@ fn start_compute_thread(filename: String, mut device: Device) -> EmulatorResult<
panic!("Disconnected"); panic!("Disconnected");
} }
device.cycle().expect("Failed to execute"); device.cycle().expect("Failed to execute");
// Put a bit of delay to slow down execution
thread::sleep(Duration::from_millis(2))
} }
})?; })?;
Ok((device_termination_signal_sender, compute_handle)) Ok((device_termination_signal_sender, compute_handle))

View File

@@ -1,19 +1,72 @@
use crate::device::keyboard::KeyboardEvent;
use sdl2::video::WindowBuildError;
use sdl2::IntegerOrSdlError;
use std::fmt::Display;
use std::sync::mpsc::SendError; use std::sync::mpsc::SendError;
use std::sync::PoisonError; use std::sync::PoisonError;
use sdl2::IntegerOrSdlError; use std::time::Duration;
use sdl2::video::WindowBuildError;
use crate::device::keyboard::KeyboardEvent;
pub type EmulatorResult<T> = Result<T, EmulatorError>; pub type EmulatorResult<T> = Result<T, EmulatorError>;
/// CHIP-8 Device configuration
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct DeviceConfig {
is_new_chip8: bool,
halt_on_invalid: bool,
/// None if disabled, target instruction time otherwise
throttling_time: Option<Duration>,
}
impl DeviceConfig {
pub fn new(
is_new_chip8: bool,
halt_on_invalid: bool,
do_instruction_throttling: bool,
ips_throttling_rate: u64,
) -> DeviceConfig {
DeviceConfig {
is_new_chip8,
halt_on_invalid,
throttling_time: if do_instruction_throttling {
Some(Duration::from_micros(1_000_000 / ips_throttling_rate))
} else {
None
},
}
}
pub fn is_new_chip8(&self) -> bool {
self.is_new_chip8
}
pub fn should_halt_on_invalid(&self) -> bool {
self.halt_on_invalid
}
pub fn get_throttling_config(&self) -> Option<Duration> {
self.throttling_time
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum EmulatorError { pub enum EmulatorError {
SdlError(String), SdlError(String),
IOError(String), IOError(String),
MutexInvalidState(String) MutexInvalidState(String),
} }
impl Display for EmulatorError{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self{
EmulatorError::SdlError(err) => write!(f,"Error with SDL: {}",err),
EmulatorError::IOError(io_err) => write!(f,"IO Error: {}",io_err),
EmulatorError::MutexInvalidState(invalid_mutex_err) => write!(f,"Issue from mutex: {}",invalid_mutex_err),
}
}
}
impl From<SendError<()>> for EmulatorError{
fn from(value: SendError<()>) -> Self {
Self::IOError(String::from("Could not update as: ")+value.to_string().as_str())
}
}
impl From<String> for EmulatorError { impl From<String> for EmulatorError {
fn from(value: String) -> Self { fn from(value: String) -> Self {
Self::SdlError(value) Self::SdlError(value)
@@ -29,24 +82,50 @@ impl From<WindowBuildError> for EmulatorError {
impl From<IntegerOrSdlError> for EmulatorError { impl From<IntegerOrSdlError> for EmulatorError {
fn from(value: IntegerOrSdlError) -> Self { fn from(value: IntegerOrSdlError) -> Self {
match value { match value {
IntegerOrSdlError::IntegerOverflows(x, y) => { Self::SdlError(format!("{} - {}", x, y)) } IntegerOrSdlError::IntegerOverflows(x, y) => Self::SdlError(format!("{} - {}", x, y)),
IntegerOrSdlError::SdlError(str) => { Self::SdlError(str) } IntegerOrSdlError::SdlError(str) => Self::SdlError(str),
} }
} }
} }
impl From<std::io::Error> for EmulatorError{ impl From<std::io::Error> for EmulatorError {
fn from(value: std::io::Error) -> Self { fn from(value: std::io::Error) -> Self {
Self::IOError(value.to_string()) Self::IOError(value.to_string())
} }
} }
impl<T> From<PoisonError<T>> for EmulatorError{ impl<T> From<PoisonError<T>> for EmulatorError {
fn from(value: PoisonError<T>) -> Self { fn from(value: PoisonError<T>) -> Self {
Self::MutexInvalidState(value.to_string()) Self::MutexInvalidState(value.to_string())
} }
} }
impl From<SendError<KeyboardEvent>> for EmulatorError{ impl From<SendError<KeyboardEvent>> for EmulatorError {
fn from(value: SendError<KeyboardEvent>) -> Self { fn from(value: SendError<KeyboardEvent>) -> Self {
Self::IOError(format!("Failed to communicate keyboard event to main thread: {}",value)) Self::IOError(format!(
"Failed to communicate keyboard event to main thread: {}",
value
))
}
}
#[cfg(test)]
mod tests{
use std::time::Duration;
use super::DeviceConfig;
#[test]
fn test_device_config_all_false(){
let device_config = DeviceConfig::new(false, false, false, 800);
assert_eq!(false,device_config.is_new_chip8());
assert_eq!(false,device_config.should_halt_on_invalid());
assert!(device_config.get_throttling_config().is_none());
}
#[test]
fn test_device_config_throttling_enabled_and_new_chip8(){
let device_config = DeviceConfig::new(true, false, true, 100);
const EXPECTED_INSTRUCTION_TIME_MS:u64 = 10;
assert_eq!(true,device_config.is_new_chip8());
assert_eq!(false,device_config.should_halt_on_invalid());
assert_eq!(Some(Duration::from_millis(EXPECTED_INSTRUCTION_TIME_MS)),device_config.get_throttling_config());
} }
} }