I have a question related to bits 0,1,2 of the $F1 control register in the S-SMP.
There is some conflicting information on exactly what condition/action causes the timer registers to be reset. Anomie's
spc700.txt states:
Quote:
When transitioning from 0 to 1, the timer Stage 2 and 3 counters are both reset to 0.
However, nocash's
fullsnes.txt states:
Quote:
(0=Disable, set TnOUT=0 & reload divider, 1=Enable)
So nocash is basically stating the exact opposite of what Anomie states (i.e. writing a 0 performs the reset of the timer register, while writing a 1 just enables the timer).
In addition, the
SPC700 Reference states:
Quote:
Writing to the control register will ALWAYS reset active timers.
So the SPC-700 reference seems to think that writing any value whatsoever to the $F1 control register will cause all of the timers to reset.
Can someone please clarify which of the above is correct?
This verifies Anomie's doc for the stage 3 counter (the one readable at T0OUT). Values in comment are what is logged when run on hardware.
Code:
main:
; begin_test delays so that T0OUT has incremented twice
call begin_test
mov a, T0OUT
call log_a ; 02
; Writing $01 to CONTROL when it's already $01 doesn't clear timer
call begin_test
mov CONTROL, #$01
mov a, T0OUT
call log_a ; 02
; Writing $00 to CONTROl when it was $01 doesn't clear timer
call begin_test
mov CONTROL, #$00
mov a, T0OUT
call log_a ; 02
; Writing $00 to CONTROL stops timer, so we only get 02 rather than 04
call begin_test
mov CONTROL, #$00
call delay_256
mov a, T0OUT
call log_a ; 02
; Writing $01 to CONTROL when it was $00 clears T0OUT
call begin_test
mov CONTROL, #$00
call delay_256
; T0OUT is still 02
mov CONTROL, #$01
mov a, T0OUT
call log_a ; 00
call print_log
ret
begin_test:
mov CONTROL, #$01
mov T0TARGET, #1
call sync_timer0_coarse
call delay_256
ret
sync_timer0_coarse:
mov a, T0OUT
: mov a, T0OUT
beq :-
ret
delay_256:
call !delay_128
delay_128:
call !delay_64
delay_64:
call !delay_32
delay_32:
call !delay_16
delay_16: ; 8 call
notc ; 3
ret ; 5
And the other issue, when the divider is reset, makes no difference as far as I can see. If it's cleared on CONTROL.0 1->0, then you won't be able to see the effect until after you do 0->1 to start the timer again. And if it's cleared on 0->1, then you'll see it just as soon. Either way, this confirms that on one of those transitions it's reset. BTW, I've worked with Anomie in the past and found his documentation to be the most authoritative. You can't really gather all the various descriptions of behavior and expect them to agree, and if you treat a disagreement as a lack of any reliable information, you're setting yourself up for lots of unnecessary work.
Code:
main:
; begin_test delays so after 128 cycles T0OUT has incremented once
call begin_test
call delay_128
mov a, T0OUT
call log_a ; 01
; Writing $00 to CONTROL stops increment of divider
call begin_test
mov CONTROL, #$00
call delay_128
mov a, T0OUT
call log_a ; 00
; Writing $00 then $01 to CONTROL resets divider
call begin_test
mov CONTROL, #$00
mov CONTROL, #$01
call delay_128
call delay_128
call delay_128
call delay_128
call delay_128
call delay_128
call delay_128
mov a, T0OUT
call log_a ; 00
call print_log
ret
begin_test:
mov CONTROL, #$01
mov T0TARGET, #8
call sync_timer0_coarse
call delay_128
call delay_128
call delay_128
call delay_128
call delay_128
call delay_128
call delay_128
ret
sync_timer0_coarse:
mov a, T0OUT
: mov a, T0OUT
beq :-
ret
delay_128:
call !delay_64
delay_64:
call !delay_32
delay_32:
call !delay_16
delay_16: ; 8 call
notc ; 3
ret ; 5
Thanks a bunch for the info Blargg. Let me implement the timers as best I can with this information and then I will compile the example code you have provided and make sure I get the same results.
You don't already happen to have this example code as part of a test ROM would you? I was thinking these timers were going to be very straightforward and simple...and while they aren't difficult per-se there is a lot of stuff going on that's really easy to screw up. An "SMP Timer Test ROM" sure would be awesome....hint hint
Heh, yeah, I can just alter the above code slightly and it becomes a test ROM (I'm still working on the rewrite of the SPC-700 CPU tests BTW).
jwdonal wrote:
I have a question related to bits 0,1,2 of the $F1 control register in the S-SMP.
There is some conflicting information on exactly what condition/action causes the timer registers to be reset. Anomie's
spc700.txt states:
Quote:
When transitioning from 0 to 1, the timer Stage 2 and 3 counters are both reset to 0.
However, nocash's
fullsnes.txt states:
Quote:
(0=Disable, set TnOUT=0 & reload divider, 1=Enable)
So nocash is basically stating the exact opposite of what Anomie states (i.e. writing a 0 performs the reset of the timer register, while writing a 1 just enables the timer).
My info is a bit different, but not the exact opposite. As far as I remember, the timer is "reset-to-0" while and as long as the enable bit is 0. Ie. during enable=0, and also up to (as anomie said) until the enable 0-to-1 transition.
In practice, the reset-to-0 upon 0-to-1 transition might be the most relevant part for most games. Though a few games might also rely on whether the timers are reset-to-0 during enable=0. Would be glad if somebody could re-verify if the hardware does really work like so.
I think we need to be precise on exactly what we mean when we say "timer". Does "timer" mean stage 2, stage 3, or both?
Like Blargg said, when the divider (i.e. stage 2) gets reset is ultimately irrelevant, since there is no way to read the current divider value. The critical piece of info is when does stage 3 (i.e. TnOUT) get reset - since that is what can be read. Anomie/Blargg say stage 3 is not reset until a 0->1 transition of the enable bit. Nocash says it happens immediately when the enable bit gets set to 0.
From Blargg's test code results above (and because Blargg always develops his tests on a real console) I expect that he is correct on this one. If stage 3 was reset immediately when the enable bit is set to 0 then Blargg's test results would be different.
I am always testing things on real console, too. Although... in this case... I can't find any timer-reset test code in my snes test program. Hum, correcting myself: I am not always testing (everything) on real console : - )
If blargg's "; Writing $00 to CONTROL stops timer, so we only get 02 rather than 04" test passed on real hardware, then the info in anomie's doc should be correct, and my description was wrong.
Here are the above two as test ROM SPCs, with source. Let me know if they don't run with just an SMP, as I haven't tested them with a lack of DSP yet.
spc-timer-clear.zip
Hey Blargg, just wanted to let you know that I'm working on getting these tests to pass. Think I might have some bugs. I will let you know how it goes once I get them working. I super appreciate you making these tests. Thanks a ton.
W00t! Both of these ROMs pass in my emu (which has no DSP or any other SNES chips implemented yet). Interestingly, the mess emulator fails the reset-TnOUT test with error code 6 which is:
Code:
"Writing $01 to CONTROL when it was $00 should clear T0OUT"
I have to say I'm kind of shocked that the mess emulator fails this basic test. It seems to me that not clearing the TNOUT registers properly would make pretty much all of the SNES games sound completely wrong, no?
Thanks for reporting back that they work standalone. I doubt software relies much on this, since they normally don't want to be resetting the timer (reading TnOUT clears it without resetting the divider).
One thing I noticed that was really interesting about one of the tests (can't remember which one it was now...maybe even both), was that your code would read the T0OUT register in _precisely_ the same clock cycle that my code was going to increment the T0OUT register.
The bug I had was that the clearing of the T0OUT register (due to your reading the register) was higher priority than the incrementing of the register (due to tick from Stage 2). So what would happen is your read would clear the T0OUT register over and over and get into an infinite loop since your code would never see the T0OUT register having anything other than 0 (because T0OUT kept getting cleared by your read since your read was higher priority than the increment).
So I swapped the priority so that incrementing was higher priority and the test passes fine now. It just makes it so that your code has to perform one additional read attempt before seeing the new incremented value.
But this did make me think about the real SNES and what it does if there was a read of the TnOUT register in the exact same cycle that it was supposed to be incremented by a tick from stage 2. Since your test passes on a real SNES I would imagine that incrementing of the TnOUT registers must have the higher priority.
Any ideas/thoughts/evidence regarding what happens with simultaneous read and stage-2-tick?
Reading T0OUT is an atomic get-then-clear operation. The increment is never lost.
When you say "get-then-clear" do you mean "get-then-in-the-next-clock-cycle-clear"?
It's not possible in hardware to have a counter that you increment _and_ clear in the same cycle. It can only do one or the other in each clock cycle, not both. If it's the case that it just clears in the next clock cycle (which is what I think you mean) then that's really easy to do.
Quote:
When you say "get-then-clear" do you mean "get-then-in-the-next-clock-cycle-clear"?
I mean that the increment can
never happen between the get and clear, in the software model.
Quote:
It's not possible in hardware to have a counter that you increment _and_ clear in the same cycle. It can only do one or the other in each clock cycle, not both. If it's the case that it just clears in the next clock cycle (which is what I think you mean) then that's really easy to do.
Other possibilities would have been that they ignored the impossibility of it so that if you read TnOUT on the cycle it's incremented, you got garbage, the increment was lost, the register wasn't cleared, etc. Tthere is another
timer aspect that does behave non-deterministically, giving random results with varying probability. In this case for reading TnOUT it behaves atomically in the software model. I don't know what happens at the hardware level, so I make no claims about it.
As blargg said, the important part is that the increment may not get lost, that would mess-up the audio timings.
The games are relying on polling the timer output; ie. they may be reading it once every 7 clock cycles, so there is a very good chance (1:7) that the "read-then-clear" part will collide with the "increment" part in the same clock cycle.
In hardware, the best solution might be to handle "read-then-clear" in one half of the clock cycle, and "increment" in the other half. Or use some "collision-flag" that will suppress the increment in current cycle, and, instead, force the increment to happen in next cylce. Or reset the timer to 01h instead of 00h upon collision.
I agree that the most critical thing is that the increment never gets lost. By switching the priorities I have accomplished that so I should be good. I have also confirmed that the frequency of my ticks to the stage 3 counters are correct based on the current divider value. I think that's the best I can do for now.
If in the future Blargg releases one of his uber-intensive timing tests for the SMP timers I will be sure to run it and fix things accordingly, but I think I should have things pretty close at least.
Thanks for all the help!!