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)",
|
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
|
||||||
}
|
}
|
||||||
|
@@ -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)?;
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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))
|
||||||
|
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::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());
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user