Timing how long something takes

For posting library code and examples.
doslogo
Posts: 32
Joined: Fri Dec 20, 2024 4:26 pm

Re: Timing how long something takes

Post by doslogo »

cosmicr wrote: Wed Mar 19, 2025 11:56 pm
doslogo wrote: Wed Mar 19, 2025 10:26 pm So how do one stop the timer so the full 16-bit value can be read?
Did you use SEI before getting the values? If I understand correctly, you need to use SEI before, and CLI afterwards.
The timers keep counting every cycle, no matter if interrupts are enabled or disabled. The issue is that I need atomic read access to the full 16-bit timer value.

Enabling/disabling interrupts have to do with timing instructions which this thread is about, but at one point you will have to read the current timer to calculate the cycles. The timer only stops when it reaches 0, or in case of Timer2, never stops (just wraps around endlessly). Simply put, if there is no way to stop the VIA timer, it can only be used to time things you already know the time of. We don't know how long the thing we'll time will take before we time it.
DragWx
Posts: 382
Joined: Tue Mar 07, 2023 9:07 pm

Re: Timing how long something takes

Post by DragWx »

It doesn't look like the VIA allows you to read the full 16-bit counter at once, so you'll always be at risk of a decrement occurring between reads. The only danger would be if L borrows from H while you're reading, but I think this can be mitigated:

Code: Select all

; Read H and L as quickly as possible.
 lda V_T1_H
 ldx V_T1_L
; Check if L borrowed from H in the time it took to read.
 cmp V_T1_H
 beq :+
; If it did, reread.
 lda V_T1_H
 ldx V_T1_L
:
; A and X contains a valid timer counter value.
If a borrow happened and you need to reread, then I don't think the counter can be clocked fast enough to cause another borrow to happen during the reread, so this should be safe.
doslogo
Posts: 32
Joined: Fri Dec 20, 2024 4:26 pm

Re: Timing how long something takes

Post by doslogo »

DragWx wrote: Thu Mar 20, 2025 3:26 am It doesn't look like the VIA allows you to read the full 16-bit counter at once, so you'll always be at risk of a decrement occurring between reads. The only danger would be if L borrows from H while you're reading, but I think this can be mitigated:

Code: Select all

; Read H and L as quickly as possible.
 lda V_T1_H
 ldx V_T1_L
; Check if L borrowed from H in the time it took to read.
 cmp V_T1_H
 beq :+
; If it did, reread.
 lda V_T1_H
 ldx V_T1_L
:
; A and X contains a valid timer counter value.
If a borrow happened and you need to reread, then I don't think the counter can be clocked fast enough to cause another borrow to happen during the reread, so this should be safe.
I am a little bit confused how you can know if L borrowed from H. The timer is constantly running for each instruction, so while you capture the high byte, the low byte is already "lost" (you lost the count by the amount of cycles it took to read from the high byte). When I think about it, knowing how many cycles the instruction takes, and adding that to the extracted low byte and carrying it to the high byte.... Nah. I feel like some external odd thing will happen to skew the result.

The idea I came up with is measuring the same thing twice, and read the low byte of the timer first time, and the high byte of the timer the second time, and combine those to get the final result. It requires Timer2 because the timer has to wrap from $0000 to $FFFF and not stop, and need to reset with one instruction, and nothing external must interfere with the measurement, which is why I measure inside an ISR. Also, the result will be +2 cycles because of how the VIA continuous timer works.
DragWx
Posts: 382
Joined: Tue Mar 07, 2023 9:07 pm

Re: Timing how long something takes

Post by DragWx »

doslogo wrote: Thu Mar 20, 2025 4:41 pm I am a little bit confused how you can know if L borrowed from H. The timer is constantly running for each instruction, so while you capture the high byte, the low byte is already "lost" (you lost the count by the amount of cycles it took to read from the high byte). When I think about it, knowing how many cycles the instruction takes, and adding that to the extracted low byte and carrying it to the high byte.... Nah. I feel like some external odd thing will happen to skew the result.

Code: Select all

 lda V_T1_H
 ldx V_T1_L
 cmp V_T1_H
If T1 = $0104 at the first cycle of this code, then borrow occurs between the LDA and LDX: A = $01, X = $FD. (The actual fetch occurs on the final cycle of the opcode)
The following CMP will catch that A != H, since H is now $00.
That inequality is how we know L borrowed from H in the middle of our reads. If there were no borrow, then H would still equal A.
It's ok that L is decrementing all that time, just as long as it doesn't borrow from H, because that borrow is what gives us an invalid reading, rather than a reading with just an offset.

If you want code which can provide the appropriate compensation, so you know the counter value at the start of the code, that's possible too:

Code: Select all

; START
 ldx #$0A      ; +2 = 2 -- Compensation, number of cycles from START to first L-read.
; Read H and L as quickly as possible.
 ldy V_T1_H    ; +4 = 6
 lda V_T1_L    ; +4 = 10
; Check if L borrowed from H in the time it took to read.
 cpy V_T1_H    ; +4 = 14
 beq :+        ; +2 = 16
; If it did, reread.
 ldy V_T1_H    ; +4 = 20
 lda V_T1_L    ; +4 = 24
 ldx #$18; Compensation, number of cycles from START to second L-read.
:
; Y and A contains valid counter value, now compensate for the time the read logic took.
 stx TEMP
 clc
 adc TEMP
 tax
 tya
 adc #$00
; A and X contains a valid, compensated timer counter value
This code assumes the counter is clocked once per cycle. If the counter has a different period, then divide those compensation constants by that period. For example, if clocked once every two cycles, divide the constants by two.

EDIT: Sorry, I forgot the counter is decreasing, so you'd need to add the compensation value, not subtract as my code originally did.
Post Reply