Jump to content
Karl G

What can break the timer?

Recommended Posts

I ran into a strange issue where my timer seemed to behave erratically after setting it for vertical blank. It is fine for the first 3 frames, but then after setting it, the value of INTIM seems to jump around erratically after setting TIM64T. My timer-setting is pretty normal, I think:

 

	VERTICAL_SYNC
    lda #44
    sta TIM64T

 

The "sta TIM64T" ends at cycle 9 in this case. If I insert a "nop" before the "sta TIM64T", then the timer is stable.

 

Also, possibly unrelated, but the Stella debugger shows the "sta TIM64T" as taking 4 cycles. I'm not understanding why this would be 4 cycles and not 3.

 

So - I have a workaround for this issue, but I would ideally like to understand why it was happening to begin with. Thanks in advance for any insight.

Share this post


Link to post
Share on other sites

Not sure if it is related, but I ran into a similar strange timing issue (code took 1 extra cycle) while trying to make my game screen stable.  So far the only way I could fix it was to add a "align 256" in an earlier part of the code since it appeared that part of the timing critical code crossed a page boundary. 

Share this post


Link to post
Share on other sites
1 hour ago, littaum said:

Not sure if it is related, but I ran into a similar strange timing issue (code took 1 extra cycle) while trying to make my game screen stable.  So far the only way I could fix it was to add a "align 256" in an earlier part of the code since it appeared that part of the timing critical code crossed a page boundary. 

Branching across a page boundary incurs a +1 cycle "penalty".

Share this post


Link to post
Share on other sites

Since the timer is checked only every 5 cycles, this might add an extra scanline under certain circumstances. Check if a WSYNC following the timer check loop happens very close to cycle 76.

 

Unlike the TIA registers, many RIOT registers are not part of the zeropage. Therefore they require 4 cycles for reading. 

  • Thanks 1

Share this post


Link to post
Share on other sites

Also keep in mind the behaviour of the timer flag when timer reaches zero. It's rather odd.

Here is my solution...

 

                    ldx #0
                    bit TIMINT
                    bmi .zeroTime                   ; already overtime!
                    lda INTIM
                    beq .zeroTime                   ; also time expired
                    bmi .zeroTime                   ; must have been just overtime and now counting down
                    tax
.xOSwait            sta WSYNC
                    bit TIMINT                      ; wait for the timer
                    bpl .xOSwait

.zeroTime           stx TimeLeftOS                  ; x holds the "how much time left"
                    rts

Essentially, when the timer value reaches zero (INTIM), then the TIMINT flag is set and from that point onwards the timer (INTIM) counts down at 1-clock intervals (i.e., it counts down at your step value until it reaches 0 and then it counts down at 1-clock steps.  It doesn't stop at zero.).  So the above code is trying to put "how much time left" into a variable (TimeLeftOS).  But you can't do that just by reading INTIM, as that clears any flag indicating that time was zero. So the convolutions are to first see if we are overtime - zero left. Otherwise  if timer is 0 also zero left, otherwise we store that and then wait for timer to expire, and then store and return.

Share this post


Link to post
Share on other sites

None of these really describe what I saw. Watching INTIM in the Stella debugger as I stepped through the code, the value would jump around seemingly randomly with each instruction executed, rather than decrementing every 64 cycles as expected. I've made enough changes to my other code that it no longer triggers even when I take out the "nop". If this is really an unknown issue, I can try to recreate it to see if anyone can tell what's going on for the purpose of documenting the strange behavior.

 

I guess it's a testament to Stella that it never occurred to me that it could be a Stella bug, and I'm still guessing it is not.

Share this post


Link to post
Share on other sites
42 minutes ago, Karl G said:

None of these really describe what I saw. Watching INTIM in the Stella debugger as I stepped through the code, the value would jump around seemingly randomly with each instruction executed, rather than decrementing every 64 cycles as expected. I've made enough changes to my other code that it no longer triggers even when I take out the "nop". If this is really an unknown issue, I can try to recreate it to see if anyone can tell what's going on for the purpose of documenting the strange behavior.

 

I guess it's a testament to Stella that it never occurred to me that it could be a Stella bug, and I'm still guessing it is not.

If your timer had already run out (i.e., INTIM had previously reached zero), then it would now be counting down in 1-cycle steps. So, for each instruction executed, you would have multiple counts of the timer.  In other words, if it reached 0, it would not count down in 64 cycles, but instead in 1-cycles. Are you sure that this isn't what stella is showing you?

  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites
1 hour ago, Andrew Davie said:

If your timer had already run out (i.e., INTIM had previously reached zero), then it would now be counting down in 1-cycle steps. So, for each instruction executed, you would have multiple counts of the timer.  In other words, if it reached 0, it would not count down in 64 cycles, but instead in 1-cycles. Are you sure that this isn't what stella is showing you?

I was monitoring the value of INTIM via the debugger right after I had written to TIM64T - a newly set timer. Also, it seemed to have been jumping around more chaotically than 1-tick per cycle could account for. However, I'm not having luck getting my code back in the state where it would act like this, so I guess I won't worry about it unless it happens again. Anyway, thanks for the thoughts on this.

Share this post


Link to post
Share on other sites
16 minutes ago, SpiceWare said:

This topic might be relevant. Be sure to read @alex_79's reply.

 

 

Interesting. So, it may have been that after I ran down my overscan timer, it started at $FF counting down at 1T speed, and the combo of jumping to the next frame, vertical blank processing, and writing to VBLANK and my timer equaled 255 cycles right as I did my write, causing the above. The math makes it seem like it could have been the case. It seemed like INTIM was changing more than 1 per cycle executed with each step in the debugger, but I wasn't specifically watching for that, so I could be wrong about that. Anyway, it seems like the most plausible explanation. Thanks!

  • Like 1

Share this post


Link to post
Share on other sites

I ran into the issue again (I hadn't put in any code to specifically prevent it yet), and this time I watched more closely, and the timer was indeed decrementing at a 1-tick-per-cycle rate. I'm guessing this issue is why I've seen some people add 128 to their timer values, and then loop until the INTIM is no longer negative.

Share this post


Link to post
Share on other sites
5 hours ago, Karl G said:

I'm guessing this issue is why I've seen some people add 128 to their timer values, and then loop until the INTIM is no longer negative.

While this minimizes the negative effects of a timer underflow, it also makes detecting it harder. IMO a timer underflow should be very noticeable, so that the developer can fix the problem. 

Share this post


Link to post
Share on other sites
51 minutes ago, Thomas Jentzsch said:

While this minimizes the negative effects of a timer underflow, it also makes detecting it harder. IMO a timer underflow should be very noticeable, so that the developer can fix the problem. 

I didn't think about that approach minimizing the effects of going past the timer interval, though I suppose that's the case, and perhaps that's the more common reason for that approach than avoiding the timer wraparound issue like I had assumed. I agree that minimizing/hiding going past the timer interval is the wrong approach if you care about the quality of what you are creating.

Share this post


Link to post
Share on other sites

Not gonna say "I told you so", but I will say that the code I posted should be a reliable way to not only catch the wraparound, but also put the spare-time before wraparound into another variable for you. 

Share this post


Link to post
Share on other sites

I remember an idea Nukey posted years ago. I've never done it myself, but in short it was to pad a few WSYNC's after the timer exclusively for game development. The idea is to develop a stable game and remove the them after to help guarantee you don't get close to running out of cycles. The only thing I would caution there is to not have the timer run out to close to the end of the scanline, and to keep the code align with some NOP's unless you want to do a lot more testing.

 

 

 Myself, I generally just use LDA INTIM and branch on not equal. I like to re-use the zero value from the timer if I can. Some games I've done though had way less spare cycles and then I switched to:

.waitOverscan:
    bit    TIMINT
    bpl    .waitOverscan

That is still minimal bytes. The real advantage is of course is if you go way over cycles it recovers as soon as possible, and hopefully the TV is tolerant enough not to bounce the picture.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...