gb/src/timer.rs

186 lines
4.1 KiB
Rust
Raw Normal View History

use bitfield::bitfield;
#[derive(Debug)]
pub(crate) struct Timer {
2021-06-09 18:43:46 +00:00
/// 0xFF07 | TAC - Timer Control
pub(crate) ctrl: TimerControl,
/// 0xFF05 | TIMA - Timer Counter
counter: u8,
2021-06-09 18:43:46 +00:00
/// 0xFF06 | TMA - Timer Modulo
pub(crate) modulo: u8,
2021-06-09 18:43:46 +00:00
/// 0xFF04 | DIV - Divider Register
pub(crate) divider: u16,
and_result: Option<u8>,
2021-03-21 08:03:03 +00:00
interrupt: bool,
state: State,
}
2021-01-19 04:54:38 +00:00
impl Timer {
pub(crate) fn tick(&mut self) {
use State::*;
use TimerSpeed::*;
2021-03-21 08:03:03 +00:00
2021-08-20 05:17:28 +00:00
match self.state {
2021-08-22 06:48:34 +00:00
TIMAOverflow(_) | AbortedTIMAOverflow(_) => self.next(),
2021-08-20 05:17:28 +00:00
LoadTMA => {
self.counter = self.modulo;
self.interrupt = true;
2021-08-20 05:17:28 +00:00
2021-08-22 06:48:34 +00:00
self.next();
2021-08-20 05:17:28 +00:00
}
Normal => {}
}
self.divider = self.divider.wrapping_add(1);
2021-03-21 08:03:03 +00:00
// Get Bit Position
let bit = match self.ctrl.speed() {
Hz4096 => 9,
Hz262144 => 3,
Hz65536 => 5,
Hz16384 => 7,
};
2021-03-21 08:03:03 +00:00
let bit = (self.divider >> bit) as u8 & 0x01;
let new_result = bit & self.ctrl.enabled() as u8;
if let Some(0x01) = self.and_result {
if new_result == 0x00 {
// Falling Edge, increase TIMA Register
self.inc_counter();
2021-03-21 08:03:03 +00:00
}
}
self.and_result = Some(new_result);
2021-03-21 08:03:03 +00:00
}
/// 0xFF05 | TIMA - Timer Counter
pub(crate) fn tima(&self) -> u8 {
self.counter
}
2021-03-21 08:03:03 +00:00
/// 0xFF05 | TIMA - Timer Counter
pub(crate) fn set_tima(&mut self, byte: u8) {
use State::*;
match self.state {
2021-08-20 05:17:28 +00:00
Normal | AbortedTIMAOverflow(_) => self.counter = byte,
TIMAOverflow(step) => {
2021-08-20 05:17:28 +00:00
self.counter = byte;
self.state = AbortedTIMAOverflow(step);
}
2021-08-20 05:17:28 +00:00
LoadTMA => {}
}
2021-03-21 08:03:03 +00:00
}
pub(crate) fn interrupt(&self) -> bool {
2021-03-21 08:03:03 +00:00
self.interrupt
}
pub(crate) fn set_interrupt(&mut self, value: bool) {
2021-03-21 08:03:03 +00:00
self.interrupt = value;
2021-01-19 04:54:38 +00:00
}
fn inc_counter(&mut self) {
let (sum, did_overflow) = self.counter.overflowing_add(1);
self.counter = if did_overflow { 0 } else { sum };
if did_overflow {
self.state = State::TIMAOverflow(0);
}
}
2021-08-22 06:48:34 +00:00
fn next(&mut self) {
use State::*;
self.state = match self.state {
Normal | LoadTMA => Normal,
AbortedTIMAOverflow(4) => Normal,
TIMAOverflow(4) => LoadTMA,
AbortedTIMAOverflow(step) => AbortedTIMAOverflow(step + 1),
TIMAOverflow(step) => TIMAOverflow(step + 1),
}
}
2021-01-19 04:54:38 +00:00
}
impl Default for Timer {
fn default() -> Self {
Self {
2021-06-09 18:43:46 +00:00
ctrl: Default::default(),
2021-03-21 07:01:19 +00:00
counter: 0,
2021-03-21 08:03:03 +00:00
modulo: 0,
divider: 0,
interrupt: false,
and_result: None,
state: State::Normal,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum TimerSpeed {
2021-03-21 08:03:03 +00:00
Hz4096 = 0,
Hz262144 = 1,
Hz65536 = 2,
Hz16384 = 3,
}
impl From<u8> for TimerSpeed {
fn from(byte: u8) -> Self {
match byte & 0b11 {
2021-03-21 08:03:03 +00:00
0b00 => Self::Hz4096,
0b01 => Self::Hz262144,
0b10 => Self::Hz65536,
0b11 => Self::Hz16384,
_ => unreachable!("{:#04X} is not a valid value for TimerSpeed", byte),
}
}
}
impl From<TimerSpeed> for u8 {
fn from(speed: TimerSpeed) -> Self {
speed as Self
}
}
bitfield! {
pub struct TimerControl(u8);
impl Debug;
pub enabled, set_enabled: 2;
pub from into TimerSpeed, speed, set_speed: 1, 0;
}
impl Copy for TimerControl {}
impl Clone for TimerControl {
fn clone(&self) -> Self {
*self
}
}
impl Default for TimerControl {
fn default() -> Self {
Self(0)
}
}
impl From<u8> for TimerControl {
fn from(byte: u8) -> Self {
Self(byte)
}
}
impl From<TimerControl> for u8 {
fn from(ctrl: TimerControl) -> Self {
ctrl.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State {
TIMAOverflow(u8),
AbortedTIMAOverflow(u8),
Normal,
2021-08-20 05:17:28 +00:00
LoadTMA,
}