Skip to content

Commit acd54eb

Browse files
ISSOtmavivace
andauthored
Improve documentation of timer bugs (#543)
* Update TIMA diagram with findings from @Gekkio's schematics Thanks for confirming our hypotheses! * Clarify and correct "Timer overflow behavior" Research assisted by @LIJI32, @Gekkio, and @SonoSooS. Thank you all! * Clarify DIV/TAC-related timer glitches * Update src/Timer_Obscure_Behaviour.md Co-authored-by: Eldred Habert <[email protected]> --------- Co-authored-by: Antonio Vivace <[email protected]>
1 parent 7a8bc4a commit acd54eb

7 files changed

+233
-1101
lines changed

src/Timer_Obscure_Behaviour.md

+60-96
Original file line numberDiff line numberDiff line change
@@ -32,99 +32,63 @@ On **CGB**:
3232
{{#include imgs/src/timer_tac_bug_gbc.svg:2:}}
3333
</figure>
3434

35-
Notice how the values that are connected to the inputs of the
36-
multiplexer are the values of those bits, not the carry of those bits.
37-
This is the reason of a few things:
38-
39-
- When writing to DIV, the system counter is reset to zero, so the timer is
40-
also affected.
41-
42-
- When writing to DIV, if the current output is 1 and timer is
43-
enabled, as the new value after reseting the system counter will be 0, the falling
44-
edge detector will detect a falling edge and TIMA will increase.
45-
46-
- When writing to TAC, if the previously selected multiplexer input was
47-
1 and the new input is 0, TIMA will increase too. This doesn't
48-
happen when the timer is disabled, but it also happens when disabling
49-
the timer (the same effect as writing to DIV). The following code explains the behavior in DMG and MGB.
50-
51-
```
52-
clocks_array[4] = {256, 4, 16, 64}
53-
54-
old_clocks = clocks_array[old_TAC&3]
55-
new_clocks = clocks_array[new_TAC&3]
56-
57-
old_enable = old_TAC & BIT(2)
58-
new_enable = new_TAC & BIT(2)
59-
60-
sys_clocks = system counter
61-
62-
IF old_enable == 0 THEN
63-
glitch = 0 (*)
64-
ELSE
65-
IF new_enable == 0 THEN
66-
glitch = (sys_clocks & (old_clocks/2)) != 0
67-
ELSE
68-
glitch = ((sys_clocks & (old_clocks/2)) != 0) && ((sys_clocks & (new_clocks/2)) == 0)
69-
END IF
70-
END IF
71-
```
72-
73-
The sentence marked with a (\*) has a different behaviour in GBC (AGB
74-
and AGS seem to have strange behaviour even in the other statements).
75-
When enabling the timer and maintaining the same frequency it doesn't
76-
glitch. When disabling the timer it doesn't glitch either. When another
77-
change of value happens (so timer is enabled after the write), the
78-
behaviour depends on a race condition, so it cannot be predicted for
79-
every device.
80-
81-
## Timer Overflow Behaviour
82-
83-
When TIMA overflows, the value from TMA is loaded and IF timer flag is
84-
set to 1, but this doesn't happen immediately. Timer interrupt is
85-
delayed 1 M-cycle from the TIMA overflow. The TMA reload to
86-
TIMA is also delayed. For 1 M-cycle, after overflowing TIMA, the value
87-
in TIMA is $00, not TMA. This happens only when an overflow happens, not
88-
when the upper bit goes from 1 to 0, it can't be done manually writing
89-
to TIMA, the timer has to increment itself.
90-
91-
For example (SYS here is the lower 8 bits of the system counter):
92-
93-
Timer overflows:
94-
95-
[A] [B]
96-
SYS FD FE FF |00| 01 02 03
97-
TIMA FF FF FF |00| 23 23 23
98-
TMA 23 23 23 |23| 23 23 23
99-
IF E0 E0 E0 |E0| E4 E4 E4
100-
101-
Timer doesn't overflow:
102-
103-
[C]
104-
SYS FD FE FF 00 01 02 03
105-
TIMA 45 45 45 46 46 46 46
106-
TMA 23 23 23 23 23 23 23
107-
IF E0 E0 E0 E0 E0 E0 E0
108-
109-
- During the strange cycle \[A\] you can prevent the IF flag from being
110-
set and prevent the TIMA from being reloaded from TMA by writing a value
111-
to TIMA. That new value will be the one that stays in the TIMA register
112-
after the instruction. Writing to DIV, TAC or other registers won't
113-
prevent the IF flag from being set or TIMA from being reloaded.
114-
115-
- If you write to TIMA during the M-cycle that TMA is being loaded to it
116-
\[B\], the write will be ignored and TMA value will be written to TIMA
117-
instead.
118-
119-
- If TMA is written the same M-cycle it is loaded to TIMA \[B\], TIMA is
120-
also loaded with that value.
121-
122-
- This is a guessed schematic to explain the priorities with registers
123-
TIMA and TMA:
124-
125-
![](imgs/timer_tima_tma_detailed.svg "imgs/timer_tima_tma_detailed.svg")
126-
127-
TMA is a latch. As soon as it is written, the output shows that value.
128-
That explains that when TMA is written and TIMA is being incremented,
129-
the value written to TMA is also written to TIMA. It doesn't affect the
130-
IF flag though.
35+
Notice how the bits themselves are connected to the multiplexer and then to the falling-edge detector; this causes a few odd behaviors:
36+
37+
- 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](<#DIV-APU>)" pulse early.
38+
- 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.
39+
(For example: if the system counter is equal to \$3FF0 and `TAC` to \$FC, writing \$05 or \$06 to `TAC` will instantly send a "Timer tick", but \$04 or \$07 won't.)
40+
- On monochrome consoles, disabling the timer if the currently selected bit is set, will send a "Timer tick" once.
41+
This does not happen on Color models.
42+
- On Color models, a write to `TAC` that fulfills the previous bullet's conditions *and* turns the timer on (it was disabled before) may or may not send a "Timer tick".
43+
The exact behaviour varies between individual consoles.
44+
45+
## Timer overflow behavior
46+
47+
When `TIMA` overflows, the value from `TMA` is copied, and the timer flag is set in [`IF`], but **one M-cycle later**.
48+
This means that `TIMA` is equal to \$00 for the M-cycle after it overflows.
49+
50+
This only happens when `TIMA` overflows from incrementing, it cannot be made to happen by manually writing to `TIMA`.
51+
52+
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):
53+
54+
<figure><figcaption>
55+
56+
`TIMA` overflows on cycle <var>A</var>, but the interrupt is only requested on cycle <var>B</var>:
57+
58+
</figcaption>
59+
60+
M-cycle | | ||<var>A</var>|<var>B</var>||&#8203;
61+
--------|----|----|----|--------|----|----|---
62+
`SYS` | 2B | 2C | 2D | 2E | 2F | 30 | 31
63+
`TIMA` | FE | FF | FF | **00** | 23 | 24 | 24
64+
`TMA` | 23 | 23 | 23 | 23 | 23 | 23 | 23
65+
`IF` | E0 | E0 | E0 | **E0** | E4 | E4 | E4
66+
67+
</figure>
68+
69+
Here are some unexpected behaviors:
70+
71+
1. Writing to `TIMA` during cycle <var>A</var> acts as if the overflow **didn't happen**!
72+
`TMA` will not be copied to `TIMA` (the value written will therefore stay), and bit 2 of `IF` will not be set.
73+
Writing to `DIV`, `TAC`, or other registers won't prevent the `IF` flag from being set or `TIMA` from being reloaded.
74+
2. Writing to `TIMA` during cycle <var>B</var> will be ignored; `TIMA` will be equal to `TMA` at the end of the cycle anyway.
75+
3. Writing to `TMA` during cycle <var>B</var> will have the same value copied to `TIMA` as well, on the same cycle.
76+
77+
Here is how `TIMA` and `TMA` interact:
78+
79+
{{#include imgs/src/timer_tima_tma_detailed.svg:2:}}
80+
81+
<details><summary>Explanation of the above behaviors:</summary>
82+
83+
1. Writing to `TIMA` blocks the falling edge from the increment from being detected (see the `AND` gate)[^write_edge].
84+
2. The "Load" signal stays enabled for the entirety of cycle <var>B</var>, and since `TIMA` is made of <abbr title="T-flip-flop with Asynchronous Load">TAL</abbr> cells, it's constantly copying its input.
85+
However, the "Write to TIMA" signal gets reset in the middle of the cycle, thus the multiplexer emits `TMA`'s value again; in essence, the CPU's write to `TIMA` *does* go through, but it's overwritten right after.
86+
3. As mentioned in the previous bullet point, `TIMA` constantly copies its input, so it updates together with `TMA`.
87+
This and the previous bullet point can be emulated as if `TMA` was copied to `TIMA` at the very end of the cycle, though this is not quite what's happening in hardware.
88+
89+
[^write_edge]: 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.
90+
91+
</details>
92+
93+
[`TAC`]: <#FF07 — TAC: Timer control>
94+
[`IF`]: <#FF0F — IF: Interrupt flag>

src/imgs/src/timer_simplified.svg

+2-2
Loading

src/imgs/src/timer_tac_bug_dmg.svg

+4-2
Loading

0 commit comments

Comments
 (0)