implement timer logic

This commit is contained in:
Denis-Cosmin NUTIU 2024-12-07 21:27:12 +02:00
parent f8f79bc2e6
commit cbb5c2db78
6 changed files with 42 additions and 24 deletions

Binary file not shown.

Binary file not shown.

View file

@ -7,6 +7,7 @@ use rand::Rng;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::time::Instant;
use std::{thread, time};
/// Represents the display's width in pixels.
@ -110,9 +111,12 @@ where
/// Emulation loop executes the fetch -> decode -> execute pipeline
fn emulation_loop<T>(&mut self) -> Result<(), anyhow::Error> {
let mut start_time = Instant::now();
let mut last_program_counter = self.program_counter;
loop {
// fetch instruction
self.handle_timers(&mut start_time);
// fetch instruction & decode it
let instruction = self.fetch_instruction()?;
self.program_counter += 2;
@ -121,7 +125,7 @@ where
}
last_program_counter = self.program_counter;
// decode & execute
// execute
self.execute_instruction(instruction)?;
// insert some delay
@ -129,6 +133,30 @@ where
}
}
/// Handles the timers logic.
fn handle_timers(&mut self, start_time: &mut Instant) {
// Handle 60hz timers
let elapsed_time = start_time.elapsed().as_micros();
// 16667 us which is 1/60 of a second
if elapsed_time > 16667 {
if self.delay_timer > 0 {
self.delay_timer -= 1
}
if self.sound_timer > 0 {
self.delay_timer -= 1
} else {
self.do_beep()
}
*start_time = Instant::now()
}
}
/// Should make an audible beep.
fn do_beep(&mut self) {
// beep, for now
}
/// Executes the instruction
fn execute_instruction(&mut self, instruction: Instruction) -> Result<(), anyhow::Error> {
match instruction.processor_instruction() {
ProcessorInstruction::ClearScreen => {

View file

@ -1 +1 @@
// TODO: grab keyboard keys and interrupt emulation cycle
// TODO: grab keyboard keys and interrupt emulation cycle

View file

@ -138,16 +138,6 @@ impl Instruction {
Self::grab_last_nibble(data),
)
}
/*
00EE and 2NNN:
2NNN calls the subroutine at memory location NNN. In other words, just like 1NNN,
you should set PC to NNN. However, the difference between a jump and a call is that
this instruction should first push the current PC to the stack, so the subroutine can return later.
Returning from a subroutine is done with 00EE, and it does this by removing
(popping) the last address from the stack and setting the PC to it.
*/
(0x0, 0x0, 0xE, 0xE) => ProcessorInstruction::Return,
(0x2, _, _, _) => ProcessorInstruction::Call(Self::grab_inner_data(data)),
(0x8, _, _, 0x0) => ProcessorInstruction::Set(
@ -207,15 +197,15 @@ impl Instruction {
Self::grab_first_nibble(data),
Self::grab_middle_nibble(data),
),
(0xF, _, 0x0, 0x7) => ProcessorInstruction::SetVXToDelayTimer(
Self::grab_first_nibble(data)
),
(0xF, _, 0x1, 0x5) => ProcessorInstruction::SetDelayTimer(
Self::grab_first_nibble(data)
),
(0xF, _, 0x1, 0x8) => ProcessorInstruction::SetSoundTimer(
Self::grab_first_nibble(data)
),
(0xF, _, 0x0, 0x7) => {
ProcessorInstruction::SetVXToDelayTimer(Self::grab_first_nibble(data))
}
(0xF, _, 0x1, 0x5) => {
ProcessorInstruction::SetDelayTimer(Self::grab_first_nibble(data))
}
(0xF, _, 0x1, 0x8) => {
ProcessorInstruction::SetSoundTimer(Self::grab_first_nibble(data))
}
// Unknown instruction
_ => ProcessorInstruction::UnknownInstruction,
}

View file

@ -4,16 +4,16 @@ use env_logger;
mod display;
mod emulator;
mod input;
mod instruction;
mod stack;
mod input;
fn main() -> Result<(), anyhow::Error> {
env_logger::init();
let mut emulator = Emulator::new(RatatuiDisplay::new());
emulator.emulate(String::from("./roms/3-corax+.ch8"))?;
emulator.emulate(String::from("./roms/ibm-logo.ch8"))?;
Ok(())
}