Skip to content

Commit

Permalink
Implement the timer
Browse files Browse the repository at this point in the history
General timer behaviour is implemented. This was blocked for a long time
due to counting based on cycles rather than clocks - on the DMG
processor, each cycle takes four individual clocks, and the timer
progresses on each clock.
  • Loading branch information
jgilchrist committed May 20, 2024
1 parent 4f44821 commit 0bd8604
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 25 deletions.
4 changes: 0 additions & 4 deletions scripts/run_test_roms
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ RESET="\e[0m"
run_test_rom() {
local FILENAME=$(basename "$1")

# FIXME: Re-enable the second test ROM when the timer
# is implemented.
if [[ $FILENAME =~ ^02.* ]]; then return 0; fi

printf "%-30s" "${FILENAME}"

local OUTPUT=$(./build/gbemu-test "$1" --headless --print-serial-output --exit-on-infinite-jr)
Expand Down
39 changes: 22 additions & 17 deletions src/cpu/cpu.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,31 +41,36 @@ auto CPU::execute_opcode(const u8 opcode, u16 opcode_pc) -> Cycles {
}

void CPU::handle_interrupts() {
if (interrupts_enabled) {
u8 fired_interrupts = interrupt_flag.value() & interrupt_enabled.value();

if (!fired_interrupts) { return; }
u8 fired_interrupts = interrupt_flag.value() & interrupt_enabled.value();
if (!fired_interrupts) { return; }

if (halted && fired_interrupts != 0x0) {
// TODO: Handle halt bug
halted = false;
stack_push(pc);
}

if (!interrupts_enabled) {
return;
}

bool handled_interrupt = false;
stack_push(pc);

handled_interrupt = handle_interrupt(0, interrupts::vblank, fired_interrupts);
if (handled_interrupt) { return; }
bool handled_interrupt = false;

handled_interrupt = handle_interrupt(1, interrupts::lcdc_status, fired_interrupts);
if (handled_interrupt) { return; }
handled_interrupt = handle_interrupt(0, interrupts::vblank, fired_interrupts);
if (handled_interrupt) { return; }

handled_interrupt = handle_interrupt(2, interrupts::timer, fired_interrupts);
if (handled_interrupt) { return; }
handled_interrupt = handle_interrupt(1, interrupts::lcdc_status, fired_interrupts);
if (handled_interrupt) { return; }

handled_interrupt = handle_interrupt(3, interrupts::serial, fired_interrupts);
if (handled_interrupt) { return; }
handled_interrupt = handle_interrupt(2, interrupts::timer, fired_interrupts);
if (handled_interrupt) { return; }

handled_interrupt = handle_interrupt(4, interrupts::joypad, fired_interrupts);
if (handled_interrupt) { return; }
}
handled_interrupt = handle_interrupt(3, interrupts::serial, fired_interrupts);
if (handled_interrupt) { return; }

handled_interrupt = handle_interrupt(4, interrupts::joypad, fired_interrupts);
if (handled_interrupt) { return; }
}

auto CPU::handle_interrupt(u8 interrupt_bit, u16 interrupt_vector, u8 fired_interrupts) -> bool {
Expand Down
1 change: 1 addition & 0 deletions src/gameboy.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Gameboy::Gameboy(const std::vector<u8>& cartridge_data, Options& options,
cpu(*this, options),
video(*this, options),
mmu(*this, options),
timer(*this),
serial(options),
debugger(*this, options)
{
Expand Down
4 changes: 3 additions & 1 deletion src/gameboy.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@ class Gameboy {
MMU mmu;
friend class MMU;

Timer timer;
friend class Timer;

Input input;
Serial serial;
Timer timer;

Debugger debugger;
friend class Debugger;
Expand Down
3 changes: 1 addition & 2 deletions src/mmu.cc
Original file line number Diff line number Diff line change
Expand Up @@ -435,8 +435,7 @@ void MMU::write_io(const Address& address, const u8 byte) {
return;

case 0xFF05:
/* TODO: Time control */
log_unimplemented("Wrote to timer counter");
gb.timer.set_timer(byte);
return;

case 0xFF06:
Expand Down
49 changes: 48 additions & 1 deletion src/timer.cc
Original file line number Diff line number Diff line change
@@ -1,8 +1,36 @@
#include "timer.h"

#include "definitions.h"
#include "gameboy.h"
#include "cpu/cpu.h"
#include "util/bitwise.h"

const uint CLOCKS_PER_CYCLE = 4;

Timer::Timer(Gameboy& _gb) : gb(_gb) {}

void Timer::tick(uint cycles) {
u8 new_divider = static_cast<u8>(divider.value() + cycles);
divider.set(new_divider);

clocks += cycles * CLOCKS_PER_CYCLE;

auto timer_is_on = timer_control.check_bit(2);
if (timer_is_on == 0) { return; }

auto clock_limit = clocks_needed_to_increment();

if (clocks >= clock_limit) {
clocks = clocks % clock_limit;

u8 old_timer_counter = timer_counter.value();
timer_counter.increment();

if (timer_counter.value() < old_timer_counter) {
gb.cpu.interrupt_flag.set_bit_to(2, true);
timer_counter.set(timer_modulo.value());
}
}
}

auto Timer::get_divider() const -> u8 { return divider.value(); }
Expand All @@ -11,14 +39,33 @@ auto Timer::get_timer() const -> u8 { return timer_counter.value(); }

auto Timer::get_timer_modulo() const -> u8 { return timer_modulo.value(); }

auto Timer::get_timer_control() const -> u8 { return timer_control.value(); }
// Only the bottom three bits of this register are usable
auto Timer::get_timer_control() const -> u8 { return timer_control.value() & 0x3; }

void Timer::reset_divider() {
divider.set(0x0);
}

void Timer::set_timer(u8 value) {
timer_counter.set(value);
}

void Timer::set_timer_modulo(u8 value) {
timer_modulo.set(value);
}

void Timer::set_timer_control(u8 value) {
timer_control.set(value);
}

uint Timer::clocks_needed_to_increment() {
using bitwise::check_bit;

switch (get_timer_control()) {
case 0: return CLOCK_RATE / 4096;
case 1: return CLOCK_RATE / 262144;
case 2: return CLOCK_RATE / 65536;
case 3: return CLOCK_RATE / 16384;
default: fatal_error("Invalid calculation in timer");
}
}
11 changes: 11 additions & 0 deletions src/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
#include "definitions.h"
#include "register.h"

class Gameboy;

class Timer {
public:
Timer(Gameboy& inGb);

void tick(uint cycles);

auto get_divider() const -> u8;
Expand All @@ -13,10 +17,17 @@ class Timer {
auto get_timer_control() const -> u8;

void reset_divider();
void set_timer(u8 value);
void set_timer_modulo(u8 value);
void set_timer_control(u8 value);

private:
uint clocks_needed_to_increment();

uint clocks = 0;

Gameboy& gb;

ByteRegister divider;
ByteRegister timer_counter;

Expand Down

0 comments on commit 0bd8604

Please sign in to comment.