aboutsummaryrefslogtreecommitdiffstats
path: root/src/timer.c
diff options
context:
space:
mode:
authorgbc dev <gbc@localhost>2026-07-04 22:11:10 +0200
committergbc dev <gbc@localhost>2026-07-04 22:11:10 +0200
commitbc48df9f7162a4a816fdc0edaa6ed08304008c9c (patch)
tree30129f7655c177a78e62505c55243a5c0540126f /src/timer.c
parentscaffolding: build system, cartridge loader with MBC1/2/3/5, core types (diff)
downloadsl0pboy-bc48df9f7162a4a816fdc0edaa6ed08304008c9c.tar.gz
sl0pboy-bc48df9f7162a4a816fdc0edaa6ed08304008c9c.tar.xz
sl0pboy-bc48df9f7162a4a816fdc0edaa6ed08304008c9c.zip
core emulator: SM83 CPU, MMU, timer, PPU, CGB support
Passes blargg cpu_instrs (all 11), instr_timing, mem_timing.
Diffstat (limited to 'src/timer.c')
-rw-r--r--src/timer.c82
1 files changed, 82 insertions, 0 deletions
diff --git a/src/timer.c b/src/timer.c
new file mode 100644
index 0000000..d4ee69f
--- /dev/null
+++ b/src/timer.c
@@ -0,0 +1,82 @@
+#include "timer.h"
+
+// Which DIV bit feeds the TIMA increment for each TAC clock-select.
+static const int tac_bit[4] = { 9, 3, 5, 7 };
+
+static bool timer_signal(Timer *t) {
+ if (!(t->tac & 0x04)) return false;
+ return (t->div >> tac_bit[t->tac & 3]) & 1;
+}
+
+static void set_div(GB *gb, u16 newdiv) {
+ Timer *t = &gb->timer;
+ bool old = timer_signal(t);
+ t->div = newdiv;
+ bool now = timer_signal(t);
+ // falling edge -> increment TIMA
+ if (old && !now) {
+ if (t->tima == 0xFF) {
+ t->tima = 0;
+ t->overflow_pending = true;
+ t->reload_delay = 4; // reload TMA after 4 T-cycles
+ } else {
+ t->tima++;
+ }
+ }
+}
+
+void timer_tick(GB *gb, int tcycles) {
+ Timer *t = &gb->timer;
+ for (int i = 0; i < tcycles; i++) {
+ if (t->overflow_pending) {
+ t->reload_delay--;
+ if (t->reload_delay == 0) {
+ t->tima = t->tma;
+ t->overflow_pending = false;
+ gb_request_interrupt(gb, INT_TIMER);
+ }
+ }
+ set_div(gb, t->div + 1);
+ }
+}
+
+u8 timer_read(GB *gb, u16 addr) {
+ Timer *t = &gb->timer;
+ switch (addr) {
+ case 0xFF04: return t->div >> 8;
+ case 0xFF05: return t->tima;
+ case 0xFF06: return t->tma;
+ case 0xFF07: return t->tac | 0xF8;
+ }
+ return 0xFF;
+}
+
+void timer_write(GB *gb, u16 addr, u8 val) {
+ Timer *t = &gb->timer;
+ switch (addr) {
+ case 0xFF04:
+ set_div(gb, 0); // writing DIV resets it (may cause edge)
+ break;
+ case 0xFF05:
+ // writing during reload delay cancels the reload
+ if (t->reload_delay != 0) t->overflow_pending = false;
+ if (!(t->overflow_pending && t->reload_delay == 0))
+ t->tima = val;
+ break;
+ case 0xFF06:
+ t->tma = val;
+ break;
+ case 0xFF07: {
+ bool old = timer_signal(t);
+ t->tac = val & 0x07;
+ bool now = timer_signal(t);
+ if (old && !now) {
+ // disabling can also cause an increment (glitch)
+ if (t->tima == 0xFF) {
+ t->tima = 0; t->overflow_pending = true; t->reload_delay = 4;
+ } else t->tima++;
+ }
+ break;
+ }
+ }
+}