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)",
default_value_t = true
)]
/// Use updated CHIP-8 behaviours.
pub new_chip8_behaviour: bool,
#[arg(
short='i',
@@ -20,5 +21,12 @@ pub struct Porcel8ProgramArgs {
help = "Halt on invalid instruction",
default_value_t = false
)]
/// Halt on finding invalid instruction
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::keyboard::Keyboard;
use crate::device::timer::DeviceTimerManager;
use crate::util::EmulatorResult;
use crate::util::{DeviceConfig, EmulatorResult};
use rand::random;
use rand::seq::IteratorRandom;
use std::sync::{Arc, Mutex};
@@ -14,9 +14,8 @@ pub struct Device {
pub timer: DeviceTimerManager,
pub stack: Vec<u16>,
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_config: DeviceConfig
}
impl Device {
@@ -28,8 +27,7 @@ impl Device {
timer: DeviceTimerManager,
fb: Arc<Mutex<Box<[bool; Device::FRAME_BUFFER_SIZE]>>>,
device_keyboard: Keyboard,
new_chip8_mode: bool,
halt_on_invalid: bool
device_config: DeviceConfig
) -> Device {
let memory = vec![0u8; Self::DEVICE_MEMORY_SIZE]
.into_boxed_slice()
@@ -41,10 +39,9 @@ impl Device {
memory,
frame_buffer: fb,
stack: Vec::with_capacity(16),
halt_on_invalid,
timer,
new_chip8_mode,
device_keyboard,
device_config
}
}
}
@@ -55,11 +52,6 @@ impl Device {
const FONT_DEFAULT_MEM_LOCATION_END: usize = 0x9F;
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<()> {
let time_start = std::time::Instant::now();
self.device_keyboard.update_keyboard_registers()?;
@@ -71,9 +63,9 @@ impl Device {
let instruction = Instruction::decode_instruction(instr_slice);
self.execute_instruction(instruction)?;
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);
if Self::DO_CHIP_CPU_THROTTLING {
if let Some(throttling_duration) = self.device_config.get_throttling_config() {
let instruction_time = time_start.elapsed();
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);
sleep(time_left_to_sleep_for_instruction);
}
@@ -89,7 +81,7 @@ impl Device {
match instruction {
Instruction::InvalidInstruction => {
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()));
}
},
@@ -152,7 +144,7 @@ impl Device {
}
}
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;
self.registers.pc = new_pc;
}
@@ -205,7 +197,7 @@ impl Device {
self.set_flag_register(!is_overflow);
}
Instruction::RShift(x, y) => {
if !self.new_chip8_mode {
if !self.device_config.is_new_chip8() {
self.registers.v[x] = self.registers.v[y];
}
let val = self.registers.v[x];
@@ -214,7 +206,7 @@ impl Device {
self.set_flag_register(bit_carry);
}
Instruction::LShift(x, y) => {
if !self.new_chip8_mode {
if !self.device_config.is_new_chip8() {
self.registers.v[x] = self.registers.v[y];
}
let left = self.registers.v[x];
@@ -239,7 +231,7 @@ impl Device {
let reg_value = self.registers.v[x];
let index_original = self.registers.i;
// 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;
self.set_flag_register(overflowing);
(reg_value as u16 + index_original) % 0x1000
@@ -290,7 +282,7 @@ impl Device {
let index = self.registers.i as usize;
self.memory[index..=(index + last_reg_to_store)].copy_from_slice(reg_slice);
// 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;
}
}
@@ -299,7 +291,7 @@ impl Device {
let mem_slice = &self.memory[index..=(index + last_reg_to_load)];
self.registers.v[0..=last_reg_to_load].copy_from_slice(mem_slice);
// 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;
}
}

View File

@@ -14,6 +14,7 @@ use sdl2::render::BlendMode;
use sdl2::render::WindowCanvas;
use simple_logger::SimpleLogger;
use util::DeviceConfig;
use crate::args::Porcel8ProgramArgs;
use crate::device::Device;
@@ -33,7 +34,7 @@ const WINDOW_TITLE: &str = "porcel8";
fn main() -> EmulatorResult<()> {
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");
@@ -45,8 +46,8 @@ fn main() -> EmulatorResult<()> {
let (sdl_kb_adapter, device_keyboard) = SdlKeyboardAdapter::new_keyboard();
timer.start();
let device = Device::new(timer, frame_buffer_for_device, device_keyboard, new_chip8_behaviour, halt_on_invalid);
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, device_config);
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");
}
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))

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::PoisonError;
use sdl2::IntegerOrSdlError;
use sdl2::video::WindowBuildError;
use crate::device::keyboard::KeyboardEvent;
use std::time::Duration;
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)]
pub enum EmulatorError {
SdlError(String),
IOError(String),
MutexInvalidState(String)
MutexInvalidState(String),
}
impl From<String> for EmulatorError {
@@ -29,24 +66,50 @@ impl From<WindowBuildError> for EmulatorError {
impl From<IntegerOrSdlError> for EmulatorError {
fn from(value: IntegerOrSdlError) -> Self {
match value {
IntegerOrSdlError::IntegerOverflows(x, y) => { Self::SdlError(format!("{} - {}", x, y)) }
IntegerOrSdlError::SdlError(str) => { Self::SdlError(str) }
IntegerOrSdlError::IntegerOverflows(x, y) => Self::SdlError(format!("{} - {}", x, y)),
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 {
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 {
Self::MutexInvalidState(value.to_string())
}
}
impl From<SendError<KeyboardEvent>> for EmulatorError{
impl From<SendError<KeyboardEvent>> for EmulatorError {
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());
}
}