add: configuration for throttling

This commit is contained in:
2025-02-20 07:56:25 +00:00
parent d6e813b927
commit df75a881ac
4 changed files with 99 additions and 37 deletions

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, default_value_t=true)]
pub do_instruction_throttling: bool,
/// Target Instructions per second, if throttling is enabled
#[arg(short='t',long,default_value_t=5_000u64)]
pub ips_throttling_rate: u64
} }

View File

@@ -1,7 +1,7 @@
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 rand::seq::IteratorRandom; use rand::seq::IteratorRandom;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@@ -14,9 +14,8 @@ pub struct Device {
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 {
@@ -28,8 +27,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()
@@ -41,10 +39,9 @@ impl Device {
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
} }
} }
} }
@@ -55,11 +52,6 @@ impl Device {
const FONT_DEFAULT_MEM_LOCATION_END: usize = 0x9F; const FONT_DEFAULT_MEM_LOCATION_END: usize = 0x9F;
const ROM_START: usize = 0x200; const ROM_START: usize = 0x200;
// Throttling configuration for cpu
const DO_CHIP_CPU_THROTTLING:bool = false;
const TARGET_CPU_SPEED_INSTRUCTIONS_PER_SECOND: u64 = 10000;
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()?;
@@ -71,9 +63,9 @@ impl Device {
let instruction = Instruction::decode_instruction(instr_slice); let instruction = Instruction::decode_instruction(instr_slice);
self.execute_instruction(instruction)?; self.execute_instruction(instruction)?;
let instruction_time = time_start.elapsed(); if let Some(throttling_duration) = self.device_config.get_throttling_config() {
let time_left_to_sleep_for_instruction = Self::TARGET_CPU_INSTRUCTION_TIME.checked_sub(instruction_time).unwrap_or(Duration::ZERO); let instruction_time = time_start.elapsed();
if Self::DO_CHIP_CPU_THROTTLING { let time_left_to_sleep_for_instruction = throttling_duration.checked_sub(instruction_time).unwrap_or(Duration::ZERO);
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);
} }
@@ -89,7 +81,7 @@ impl Device {
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 +144,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 +197,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 +206,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 +231,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
@@ -290,7 +282,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;
} }
} }
@@ -299,7 +291,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;
} }
} }

View File

@@ -14,6 +14,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 +34,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 +46,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)?;
@@ -111,8 +112,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,17 +1,54 @@
use crate::device::keyboard::KeyboardEvent;
use sdl2::video::WindowBuildError;
use sdl2::IntegerOrSdlError;
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 From<String> for EmulatorError { impl From<String> for EmulatorError {
@@ -29,24 +66,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());
} }
} }