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