timer is memory mapped
This commit is contained in:
parent
5c44401c68
commit
fd22f62f21
|
@ -1,8 +1,8 @@
|
|||
use self::mmio::{Apu, Gpu, Joypad, Serial};
|
||||
use self::mmio::{Apu, Gpu, Joypad, Serial, Timer};
|
||||
pub use self::rom::Rom;
|
||||
use crate::{
|
||||
processor::SplitRegister,
|
||||
util::{clear_bit, set_bit},
|
||||
util::{clear_bit, set_bit, set_or_clear_bit},
|
||||
verbose_println, Cpu,
|
||||
};
|
||||
use gilrs::Gilrs;
|
||||
|
@ -29,6 +29,7 @@ pub struct Memory {
|
|||
gpu: Gpu,
|
||||
apu: Apu,
|
||||
serial: Serial,
|
||||
timers: Timer,
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
|
@ -59,6 +60,7 @@ impl Memory {
|
|||
gpu: Gpu::new(window, enable_tile_window),
|
||||
apu: Apu::init_default(),
|
||||
serial,
|
||||
timers: Timer::init(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,6 +121,10 @@ impl Memory {
|
|||
0xFF00 => self.joypad.as_register(),
|
||||
0xFF01 => self.serial.get_queued(),
|
||||
0xFF02 => self.serial.get_control(),
|
||||
0xFF04 => self.timers.get_div(),
|
||||
0xFF05 => self.timers.get_tima(),
|
||||
0xFF06 => self.timers.get_tma(),
|
||||
0xFF07 => self.timers.get_timer_control(),
|
||||
0xFF10..0xFF40 => self.apu.get_register(address),
|
||||
0xFF40 => self.gpu.get_lcdc(),
|
||||
0xFF41 => self.gpu.get_lcd_status(),
|
||||
|
@ -148,8 +154,10 @@ impl Memory {
|
|||
}
|
||||
0xFF01 => self.serial.update_queued(data),
|
||||
0xFF02 => self.serial.update_control(data),
|
||||
0xFF04 => self.io[addr_l] = 0,
|
||||
0xFF07 => self.masked_io(addr_l, data, 0b111),
|
||||
0xFF04 => self.timers.update_div(),
|
||||
0xFF05 => self.timers.update_tima(data),
|
||||
0xFF06 => self.timers.update_tma(data),
|
||||
0xFF07 => self.timers.update_timer_control(data),
|
||||
0xFF0F => self.masked_io(addr_l, data, 0b11111),
|
||||
0xFF10..0xFF40 => self.apu.mmio_write(address, data),
|
||||
0xFF40 => self.gpu.update_lcdc(data),
|
||||
|
@ -179,7 +187,6 @@ impl Memory {
|
|||
println!("BANNED write: {data:#X} to {address:#X}");
|
||||
}
|
||||
0x0..0xFF00 | 0xFF4C..=u16::MAX => panic!("passed wrong address to set_io"),
|
||||
_ => self.io[addr_l] = data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -192,10 +199,6 @@ impl Memory {
|
|||
self.joypad.update_pressed_keys(keys, gamepads)
|
||||
}
|
||||
|
||||
pub fn div_apu_tick(&mut self) {
|
||||
self.apu.div_apu_tick();
|
||||
}
|
||||
|
||||
pub(super) fn cpu_ram_init(&mut self) {
|
||||
self.set(0xFF04, 0xAD);
|
||||
self.set(0xFF10, 0x80);
|
||||
|
@ -224,7 +227,19 @@ impl Memory {
|
|||
}
|
||||
|
||||
impl Cpu {
|
||||
pub fn advance_mmio_clocks(&mut self, steps: usize) {
|
||||
pub fn increment_timers(&mut self, machine_cycles: u8) {
|
||||
let steps = (machine_cycles as usize) * 4;
|
||||
|
||||
let timer_return = self.memory.timers.tick(steps);
|
||||
for _ in 0..timer_return.num_apu_ticks {
|
||||
self.memory.apu.div_apu_tick();
|
||||
}
|
||||
|
||||
self.memory.set(
|
||||
0xFF0F,
|
||||
set_or_clear_bit(self.memory.get(0xFF0F), 2, timer_return.timer_interrupt),
|
||||
);
|
||||
|
||||
self.memory.apu.tick(steps);
|
||||
if self.memory.serial.tick(steps) {
|
||||
self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 3));
|
||||
|
|
|
@ -1,77 +1,154 @@
|
|||
use crate::{
|
||||
processor::Cpu,
|
||||
util::{get_bit, set_bit},
|
||||
};
|
||||
use crate::util::{get_bit, set_or_clear_bit};
|
||||
|
||||
enum TimerRate {
|
||||
Sixteen,
|
||||
SixtyFour,
|
||||
TwoHundredFiftySix,
|
||||
OneThousandTwentyFour,
|
||||
}
|
||||
|
||||
impl TimerRate {
|
||||
fn from_bits(one: bool, two: bool) -> Self {
|
||||
match (one, two) {
|
||||
(true, true) => Self::TwoHundredFiftySix,
|
||||
(true, false) => Self::SixtyFour,
|
||||
(false, true) => Self::Sixteen,
|
||||
(false, false) => Self::OneThousandTwentyFour,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_bits(&self) -> u8 {
|
||||
match self {
|
||||
TimerRate::Sixteen => 0b01,
|
||||
TimerRate::SixtyFour => 0b10,
|
||||
TimerRate::TwoHundredFiftySix => 0b11,
|
||||
TimerRate::OneThousandTwentyFour => 0b00,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_num(&self) -> usize {
|
||||
match self {
|
||||
TimerRate::Sixteen => 16,
|
||||
TimerRate::SixtyFour => 64,
|
||||
TimerRate::TwoHundredFiftySix => 256,
|
||||
TimerRate::OneThousandTwentyFour => 1024,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TimerControl {
|
||||
enable: bool,
|
||||
rate: TimerRate,
|
||||
}
|
||||
|
||||
impl Default for TimerControl {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable: false,
|
||||
rate: TimerRate::TwoHundredFiftySix,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TimerReturn {
|
||||
pub num_apu_ticks: usize,
|
||||
pub timer_interrupt: bool,
|
||||
}
|
||||
|
||||
pub struct Timer {
|
||||
// 0xFF04
|
||||
div: u8,
|
||||
// 0xFF05
|
||||
tima: u8,
|
||||
// 0xFF06
|
||||
tma: u8,
|
||||
// 0xFF07
|
||||
control: TimerControl,
|
||||
div_counter: usize,
|
||||
tima_counter: usize,
|
||||
}
|
||||
|
||||
impl Timer {
|
||||
pub fn init() -> Self {
|
||||
Self {
|
||||
div_counter: 0,
|
||||
tima_counter: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this will need to change when cgb mode is implemented
|
||||
// as it uses bit 5 in double speed mode
|
||||
const AUDIO_BIT: u8 = 4;
|
||||
|
||||
impl Cpu {
|
||||
pub fn increment_timers(&mut self, machine_cycles: u8) {
|
||||
let clock_cycles = (machine_cycles as usize) * 4;
|
||||
impl Timer {
|
||||
pub fn init() -> Self {
|
||||
Self {
|
||||
div: 0,
|
||||
tima: 0,
|
||||
tma: 0,
|
||||
control: TimerControl::default(),
|
||||
div_counter: 0,
|
||||
tima_counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
self.advance_mmio_clocks(clock_cycles);
|
||||
|
||||
self.timers.div_counter += clock_cycles;
|
||||
let mut div_diff = (self.timers.div_counter / 256) as u8;
|
||||
let mut last_div = self.memory.get(0xFF04);
|
||||
let mut new_div = None;
|
||||
pub fn tick(&mut self, clock_cycles: usize) -> TimerReturn {
|
||||
self.div_counter += clock_cycles;
|
||||
let mut div_diff = (self.div_counter / 256) as u8;
|
||||
let mut last_div = self.div;
|
||||
let mut returning = TimerReturn::default();
|
||||
while div_diff > 0 {
|
||||
let div = last_div.wrapping_add(1);
|
||||
|
||||
if (div & (1 << AUDIO_BIT)) < (last_div & (1 << AUDIO_BIT)) {
|
||||
// trigger DIV-APU
|
||||
self.memory.div_apu_tick();
|
||||
returning.num_apu_ticks += 1;
|
||||
}
|
||||
|
||||
new_div = Some(div);
|
||||
self.div = div;
|
||||
last_div = div;
|
||||
div_diff -= 1;
|
||||
}
|
||||
self.timers.div_counter %= 256;
|
||||
if let Some(div) = new_div {
|
||||
self.memory.set(0xFF04, div)
|
||||
};
|
||||
self.div_counter %= 256;
|
||||
|
||||
let (timer_enabled, timer_rate) = self.timer_scale();
|
||||
if timer_enabled {
|
||||
self.timers.tima_counter += clock_cycles;
|
||||
let tima_diff = (self.timers.tima_counter / timer_rate) as u8;
|
||||
self.timers.tima_counter %= timer_rate;
|
||||
let (val, wrap) = self.memory.get(0xFF05).overflowing_add(tima_diff);
|
||||
if self.control.enable {
|
||||
self.tima_counter += clock_cycles;
|
||||
let tima_diff = (self.tima_counter / self.control.rate.as_num()) as u8;
|
||||
self.tima_counter %= self.control.rate.as_num();
|
||||
let (val, wrap) = self.tima.overflowing_add(tima_diff);
|
||||
if wrap {
|
||||
self.memory.set(0xFF05, self.memory.get(0xFF06));
|
||||
self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 2));
|
||||
self.tima = self.tma;
|
||||
returning.timer_interrupt = true;
|
||||
} else {
|
||||
self.memory.set(0xFF05, val);
|
||||
self.tima = val;
|
||||
}
|
||||
}
|
||||
returning
|
||||
}
|
||||
|
||||
fn timer_scale(&self) -> (bool, usize) {
|
||||
let timer_control = self.memory.get(0xFF07);
|
||||
let timer_enable = get_bit(timer_control, 2);
|
||||
let timer_rate = match (get_bit(timer_control, 1), get_bit(timer_control, 0)) {
|
||||
(true, true) => 256,
|
||||
(true, false) => 64,
|
||||
(false, true) => 16,
|
||||
(false, false) => 1024,
|
||||
};
|
||||
(timer_enable, timer_rate)
|
||||
pub fn update_div(&mut self) {
|
||||
self.div = 0;
|
||||
}
|
||||
|
||||
pub fn get_div(&self) -> u8 {
|
||||
self.div
|
||||
}
|
||||
|
||||
pub fn update_tima(&mut self, data: u8) {
|
||||
self.tima = data;
|
||||
}
|
||||
|
||||
pub fn get_tima(&self) -> u8 {
|
||||
self.tima
|
||||
}
|
||||
|
||||
pub fn update_tma(&mut self, data: u8) {
|
||||
self.tma = data;
|
||||
}
|
||||
|
||||
pub fn get_tma(&self) -> u8 {
|
||||
self.tma
|
||||
}
|
||||
|
||||
pub fn update_timer_control(&mut self, data: u8) {
|
||||
self.control.enable = get_bit(data, 2);
|
||||
self.control.rate = TimerRate::from_bits(get_bit(data, 1), get_bit(data, 0));
|
||||
}
|
||||
|
||||
pub fn get_timer_control(&self) -> u8 {
|
||||
set_or_clear_bit(!0b111 | self.control.rate.as_bits(), 2, self.control.enable)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use self::memory::{mmio::Timer, Memory};
|
||||
use self::memory::Memory;
|
||||
use crate::{
|
||||
util::{clear_bit, get_bit},
|
||||
verbose_println,
|
||||
|
@ -30,7 +30,6 @@ pub struct Cpu {
|
|||
pub last_instruction: u8,
|
||||
last_instruction_addr: u16,
|
||||
halted: bool,
|
||||
timers: Timer,
|
||||
gamepad_handler: Gilrs,
|
||||
cycle_start: Instant,
|
||||
}
|
||||
|
@ -46,7 +45,6 @@ impl Cpu {
|
|||
last_instruction: 0x0,
|
||||
last_instruction_addr: 0x0,
|
||||
halted: false,
|
||||
timers: Timer::init(),
|
||||
gamepad_handler,
|
||||
cycle_start: Instant::now(),
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue