Timer obscure behaviour
System counter
DIV is just the visible part of the system counter.
The system counter is constantly incrementing every M-cycle, unless the CPU is in STOP mode.
Timer Global Circuit
Relation between Timer and Divider register
This is a schematic of the circuit involving TAC and DIV:
On DMG:
On CGB:
Notice how the bits themselves are connected to the multiplexer and then to the falling-edge detector; this causes a few odd behaviors:
- Resetting the entire system counter (by writing to
DIV) can reset the bit currently selected by the multiplexer, thus sending a “Timer tick” and/or “DIV-APU event” pulse early. - Changing which bit of the system counter is selected (by changing the “Clock select” bits of
TAC) from a bit currently set to another that is currently unset, will send a “Timer tick” pulse. (For example: if the system counter is equal to $3FF0 andTACto $FC, writing $05 or $06 toTACwill instantly send a “Timer tick”, but $04 or $07 won’t.) - On monochrome consoles, disabling the timer if the currently selected bit is set, will send a “Timer tick” once. This does not happen on Color models.
- On Color models, a write to
TACthat fulfills the previous bullet’s conditions and turns the timer on (it was disabled before) may or may not send a “Timer tick”. The exact behaviour varies between individual consoles.
Timer overflow behavior
When TIMA overflows, the value from TMA is copied, and the timer flag is set in IF, but one M-cycle later.
This means that TIMA is equal to $00 for the M-cycle after it overflows.
This only happens when TIMA overflows from incrementing, it cannot be made to happen by manually writing to TIMA.
Here is an example; SYS represents the lower 8 bits of the system counter, and TAC is $FD (timer enabled, bit 1 of SYS selected as source):
TIMA overflows on cycle A, but the interrupt is only requested on cycle B:
| M-cycle | A | B | | ||||
|---|---|---|---|---|---|---|---|
SYS | 2B | 2C | 2D | 2E | 2F | 30 | 31 |
TIMA | FE | FF | FF | 00 | 23 | 24 | 24 |
TMA | 23 | 23 | 23 | 23 | 23 | 23 | 23 |
IF | E0 | E0 | E0 | E0 | E4 | E4 | E4 |
Here are some unexpected behaviors:
- Writing to
TIMAduring cycle A acts as if the overflow didn’t happen!TMAwill not be copied toTIMA(the value written will therefore stay), and bit 2 ofIFwill not be set. Writing toDIV,TAC, or other registers won’t prevent theIFflag from being set orTIMAfrom being reloaded. - Writing to
TIMAduring cycle B will be ignored;TIMAwill be equal toTMAat the end of the cycle anyway. - Writing to
TMAduring cycle B will have the same value copied toTIMAas well, on the same cycle.
Here is how TIMA and TMA interact:
Explanation of the above behaviors:
- Writing to
TIMAblocks the falling edge from the increment from being detected (see theANDgate)1. - The “Load” signal stays enabled for the entirety of cycle B, and since
TIMAis made of TAL cells, it’s constantly copying its input. However, the “Write to TIMA” signal gets reset in the middle of the cycle, thus the multiplexer emitsTMA’s value again; in essence, the CPU’s write toTIMAdoes go through, but it’s overwritten right after. - As mentioned in the previous bullet point,
TIMAconstantly copies its input, so it updates together withTMA. This and the previous bullet point can be emulated as ifTMAwas copied toTIMAat the very end of the cycle, though this is not quite what’s happening in hardware.
This is necessary, because otherwise writing a number with bit 7 reset (either from the CPU or from TMA) when TIMA’s bit 7 is set, would trigger the bit 7 falling edge detector and thus schedule a spurious interrupt.