diff --git a/src/Timer_Obscure_Behaviour.md b/src/Timer_Obscure_Behaviour.md index 236621af..651ade0c 100644 --- a/src/Timer_Obscure_Behaviour.md +++ b/src/Timer_Obscure_Behaviour.md @@ -32,99 +32,63 @@ On **CGB**: {{#include imgs/src/timer_tac_bug_gbc.svg:2:}} -Notice how the values that are connected to the inputs of the -multiplexer are the values of those bits, not the carry of those bits. -This is the reason of a few things: - -- When writing to DIV, the system counter is reset to zero, so the timer is -also affected. - -- When writing to DIV, if the current output is 1 and timer is -enabled, as the new value after reseting the system counter will be 0, the falling -edge detector will detect a falling edge and TIMA will increase. - -- When writing to TAC, if the previously selected multiplexer input was -1 and the new input is 0, TIMA will increase too. This doesn't -happen when the timer is disabled, but it also happens when disabling -the timer (the same effect as writing to DIV). The following code explains the behavior in DMG and MGB. - -``` - clocks_array[4] = {256, 4, 16, 64} - - old_clocks = clocks_array[old_TAC&3] - new_clocks = clocks_array[new_TAC&3] - - old_enable = old_TAC & BIT(2) - new_enable = new_TAC & BIT(2) - - sys_clocks = system counter - - IF old_enable == 0 THEN - glitch = 0 (*) - ELSE - IF new_enable == 0 THEN - glitch = (sys_clocks & (old_clocks/2)) != 0 - ELSE - glitch = ((sys_clocks & (old_clocks/2)) != 0) && ((sys_clocks & (new_clocks/2)) == 0) - END IF - END IF -``` - -The sentence marked with a (\*) has a different behaviour in GBC (AGB -and AGS seem to have strange behaviour even in the other statements). -When enabling the timer and maintaining the same frequency it doesn't -glitch. When disabling the timer it doesn't glitch either. When another -change of value happens (so timer is enabled after the write), the -behaviour depends on a race condition, so it cannot be predicted for -every device. - -## Timer Overflow Behaviour - -When TIMA overflows, the value from TMA is loaded and IF timer flag is -set to 1, but this doesn't happen immediately. Timer interrupt is -delayed 1 M-cycle from the TIMA overflow. The TMA reload to -TIMA is also delayed. For 1 M-cycle, after overflowing TIMA, the value -in TIMA is $00, not TMA. This happens only when an overflow happens, not -when the upper bit goes from 1 to 0, it can't be done manually writing -to TIMA, the timer has to increment itself. - -For example (SYS here is the lower 8 bits of the system counter): - - Timer overflows: - - [A] [B] - SYS FD FE FF |00| 01 02 03 - TIMA FF FF FF |00| 23 23 23 - TMA 23 23 23 |23| 23 23 23 - IF E0 E0 E0 |E0| E4 E4 E4 - - Timer doesn't overflow: - - [C] - SYS FD FE FF 00 01 02 03 - TIMA 45 45 45 46 46 46 46 - TMA 23 23 23 23 23 23 23 - IF E0 E0 E0 E0 E0 E0 E0 - -- During the strange cycle \[A\] you can prevent the IF flag from being -set and prevent the TIMA from being reloaded from TMA by writing a value -to TIMA. That new value will be the one that stays in the TIMA register -after the instruction. Writing to DIV, TAC or other registers won't -prevent the IF flag from being set or TIMA from being reloaded. - -- If you write to TIMA during the M-cycle that TMA is being loaded to it -\[B\], the write will be ignored and TMA value will be written to TIMA -instead. - -- If TMA is written the same M-cycle it is loaded to TIMA \[B\], TIMA is -also loaded with that value. - -- This is a guessed schematic to explain the priorities with registers -TIMA and TMA: - -![](imgs/timer_tima_tma_detailed.svg "imgs/timer_tima_tma_detailed.svg") - -TMA is a latch. As soon as it is written, the output shows that value. -That explains that when TMA is written and TIMA is being incremented, -the value written to TMA is also written to TIMA. It doesn't affect the -IF flag though. +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](<#DIV-APU>)" 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 and `TAC` to \$FC, writing \$05 or \$06 to `TAC` will 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 `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". + 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: + +1. Writing to `TIMA` during cycle A acts as if the overflow **didn't happen**! + `TMA` will not be copied to `TIMA` (the value written will therefore stay), and bit 2 of `IF` will not be set. + Writing to `DIV`, `TAC`, or other registers won't prevent the `IF` flag from being set or `TIMA` from being reloaded. +2. Writing to `TIMA` during cycle B will be ignored; `TIMA` will be equal to `TMA` at the end of the cycle anyway. +3. Writing to `TMA` during cycle B will have the same value copied to `TIMA` as well, on the same cycle. + +Here is how `TIMA` and `TMA` interact: + +{{#include imgs/src/timer_tima_tma_detailed.svg:2:}} + +
Explanation of the above behaviors: + +1. Writing to `TIMA` blocks the falling edge from the increment from being detected (see the `AND` gate)[^write_edge]. +2. The "Load" signal stays enabled for the entirety of cycle B, and since `TIMA` is 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 emits `TMA`'s value again; in essence, the CPU's write to `TIMA` *does* go through, but it's overwritten right after. +3. As mentioned in the previous bullet point, `TIMA` constantly copies its input, so it updates together with `TMA`. + 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. + +[^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. + +
+ +[`TAC`]: <#FF07 — TAC: Timer control> +[`IF`]: <#FF0F — IF: Interrupt flag> diff --git a/src/imgs/src/timer_simplified.svg b/src/imgs/src/timer_simplified.svg index 72d7c6d4..6c73312b 100644 --- a/src/imgs/src/timer_simplified.svg +++ b/src/imgs/src/timer_simplified.svg @@ -7,9 +7,9 @@ .right { text-anchor: end; } rect, polyline, line, circle { stroke: var(--fg, #000); fill: none; } #arrow-head { stroke: none; fill: var(--fg, #000); } - polygon { stroke: var(--fg, #000); fill: var(--quote-bg, #ccc); } + polygon, .reg { stroke: var(--fg, #000); fill: var(--quote-bg, #ccc); } .inverted { stroke: var(--bg, #fff); fill: var(--bg, #fff); } - .placeholder { stroke-dasharray: 5 3 2 0; fill: #40ffff30; } + .placeholder { stroke-dasharray: 5 3 2 0; fill: #40ffff30; } .event { fill: #00ff0020; } .mux { fill: #10e8e850; } ]]> diff --git a/src/imgs/src/timer_tac_bug_dmg.svg b/src/imgs/src/timer_tac_bug_dmg.svg index d7420485..1adec2b2 100644 --- a/src/imgs/src/timer_tac_bug_dmg.svg +++ b/src/imgs/src/timer_tac_bug_dmg.svg @@ -8,9 +8,9 @@ .right { text-anchor: end; } rect, polyline, line, circle { stroke: var(--fg, #000); fill: none; } #arrow-head { stroke: none; fill: var(--fg, #000); } - polygon { stroke: var(--fg, #000); fill: var(--quote-bg, #ccc); } + polygon, .reg { stroke: var(--fg, #000); fill: var(--quote-bg, #ccc); } .inverted { stroke: var(--bg, #fff); fill: var(--bg, #fff); } - .placeholder { stroke-dasharray: 5 3 2 0; fill: #40ffff30; } + .placeholder { stroke-dasharray: 5 3 2 0; fill: #40ffff30; } .event { fill: #00ff0020; } .mux { fill: #10e8e850; } ]]> @@ -45,6 +45,7 @@ M-cycle clock + @@ -64,6 +65,7 @@ TAC.freq TAC.enable + AND diff --git a/src/imgs/src/timer_tac_bug_gbc.svg b/src/imgs/src/timer_tac_bug_gbc.svg index b94a8a7b..c52b4b2e 100644 --- a/src/imgs/src/timer_tac_bug_gbc.svg +++ b/src/imgs/src/timer_tac_bug_gbc.svg @@ -1,79 +1,81 @@ - - - - - - - - - - - - 76 - 54 - 32 - 10 - - - - - DIV - - - - - Reset - - Write - to DIV - - Increment - - M-cycle - clock - - - - - - 0 - 3 - 2 - 1 - - - - 2 - 10 - TAC - - TAC.freq - - TAC.enable - - - Falling - edge - detector - - - AND - - - Timer - tick + + + + + + + + + + + + 76 + 54 + 32 + 10 + + + + + DIV + + + + + Reset + + Write + to DIV + + Increment + + M-cycle + clock + + + + + + + 0 + 3 + 2 + 1 + + + + 2 + 10 + TAC + + TAC.freq + + TAC.enable + + + + Falling + edge + detector + + + AND + + + Timer + tick diff --git a/src/imgs/src/timer_tima_tma_detailed.svg b/src/imgs/src/timer_tima_tma_detailed.svg new file mode 100644 index 00000000..a8911c15 --- /dev/null +++ b/src/imgs/src/timer_tima_tma_detailed.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + 76 + 54 + 32 + 10 + + + + + TMA + + + + Write + to TMA + + Load + + CPU data bus + + + 0 + 1 + + + TIMA + + Timer + tick + + Increment + + + Falling + edge + detector + + + Write to + TIMA + + + + NOT + + + AND + + + Delay + + Set + + + 4 + 32 + 10 + IF + + + + OR + + Load + diff --git a/src/imgs/timer_tima_tma_detailed.graphml b/src/imgs/timer_tima_tma_detailed.graphml deleted file mode 100644 index 52bc608e..00000000 --- a/src/imgs/timer_tima_tma_detailed.graphml +++ /dev/null @@ -1,689 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - TIMA_Write - - - - - - - - - - - - - - - - - TMA - - - - - - - - - - - - - - - - - TIMA - - - - - - - - - - - - - - - - - 0 - - - - - - - - - - - - - - - - - 1 - - - - - - - - - - - - - - - - - 2 - - - - - - - - - - - - - - - - - 3 - - - - - - - - - - - - - - - - - 4 - - - - - - - - - - - - - - - - - 5 - - - - - - - - - - - - - - - - - 6 - - - - - - - - - - - - - - - - - 7 - - - - - - - - - - - - - - - - - IF - - - - - - - - - - - - - - - - - Timer -INC - - - - - - - - - - - - - - - - - AND - - - - - - - - - - - - - - - - - Delay - - - - - - - - - - - - - - - - - NOT - - - - - - - - - - - - - - - - - 0 1 - - - - - - - - - - - - - - - - - 0 1 - - - - - - - - - - - - - - - - - ADD - - - - - - - - - - - - - - - - - 1 - - - - - - - - - - - - - - - - - OR - - - - - - - - - - - - - - - - - DATA BUS - - - - - - - - - - - - - - - - - TMA_Write - - - - - - - - - - - - - - - - - OR - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Carry bit 7 - - - - - - - - - - - - - - - - Set IF flag - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Enable - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Latch - - - - - - - - - - - - - - - - - - - - - - - - - - - Set IF flag -Load TIMA from TMA - - - - - - - - - - - - - - - - Load TIMA -from TMA - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/imgs/timer_tima_tma_detailed.svg b/src/imgs/timer_tima_tma_detailed.svg deleted file mode 100644 index 50a8ad62..00000000 --- a/src/imgs/timer_tima_tma_detailed.svg +++ /dev/null @@ -1,236 +0,0 @@ - - - - - - - - - - - - - - - - - - - - TIMA_Write - - - - - - - TMA - - - - - - - TIMA - - - - - - - 0 - - - - - - - 1 - - - - - - - 2 - - - - - - - 3 - - - - - - - 4 - - - - - - - 5 - - - - - - - 6 - - - - - - - 7 - - - - - - - IF - - - - - - - Timer - INC - - - - - - - AND - - - - - - - Delay - - - - - - - NOT - - - - - - - 0 1 - - - - - - - 0 1 - - - - - - - ADD - - - - - - - 1 - - - - - - - OR - - - - - - - DATA BUS - - - - - - - TMA_Write - - - - - - - OR - - - - Set IF flag - - - - - - - - - - - - - - - - - - - Carry bit 7 - - - - - - - - - - - Load TIMA - from TMA - - - Enable - - - - - - - Latch - - - Set IF flag - Load TIMA from TMA - - - - -