add: configuration for throttling
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
@@ -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)?;
|
||||
|
||||
if let Some(throttling_duration) = self.device_config.get_throttling_config() {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -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))
|
||||
|
83
src/util.rs
83
src/util.rs
@@ -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());
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user