Metropoli BBS
VIEWER: timold.s MODE: TEXT (ASCII)
**********************************************************************
* Name:		TIM
* Stack:	( ob --> ? time )
* Desc:		Execution time measurement
**********************************************************************
ASSEMBLE
	CON(1)	8
RPL
xNAME TIM
::
  CK1

  GX? NOTcase :: "Not on SX" DO$EXIT ;

* Equal oppoturnity for all programs

  SysTime 1LAMBIND			( Just allocate correct size )
  GARBAGE

  '

  ::
    CODE
TIMEPART1	GOSBVL	=SAVPTR

* First make sure executing TIM itself by a keypress doesn't
* disturb the measurement

		GOSBVL	=DisableIntr
-		GOSBVL	=BITMAP
		GOSBVL	=Debounce
		?A#0	W
		GOYES	-
		GOSBVL	=AllowIntr
		LC(5)	#1A00		Good experimental delay on GX
-		C=C-1	A		to cool things down
		GONC	-

* Now synchronize to tick boundary to get best possible start time

		GOSUB	TimGetLam
		GOSUB	Synchronize
		DAT0=C	13		Store the start time
		GOSBVL	=GETPTR
		A=DAT1	A		Pop the program
		D1=D1+	5
		D=D+1	A
		PC=(A)			And evaluate it

* Use following exit instead to measure overhead

OverHead
*		A=A+1	A
*		A=DAT0	A
*		D0=D0+	5
*		PC=(A)

* Get TIM lambda variable

TimGetLam	D0=(5)	=G_TEMPENV
		A=DAT0	A
		D0=A			->tempenv
		D0=D0+	15		->lam1
		A=DAT0	A
		D0=A			->hxs_time
		D0=D0+	10		->time
		RTN

* Synchronize code with ticking clock

Synchronize	GOSBVL	=DisableIntr
		SETHEX
		D1=(5)	=TIMERCTRL.2
		C=DAT1	1		[SRQ WKE INT RUN]
		D=0	S		Flag timer is valid
		?CBIT=1	0
		GOYES	+
		D=D+1	S		D[S]<>0 - timer not running
+		D1=(2)	=CRC
		C=0	A
		DAT1=C	4		Clear CRC for time verification
		D1=(5)	=G_NEXTIRQ
		C=DAT1	13		C[0-13]=nextirq
		D1=(5)	=CRC
		C=0	A
		C=DAT1	4		C[A]=nextirq crc (true)
		D1=(5)	=G_TIMECRC
		A=0	A
		A=DAT1	4		A[A]=nextirq crc
		?C=A	A
		GOYES	+
		D=D+1	S		D[S]<>0 - invalid nextirq
+		D1=(4)	=G_NEXTIRQ
		A=0	W
		A=DAT1	13
		B=A	W		B[W]=nextirq
		C=0	W
		P=	0
		?D#0	S
		GOYES	skipsynch	Timer is bad - ignore synching

* Now synchronize close to display update boundary

		D1=(5)	=LINECOUNT	even address #128

* Even address

synchloop	A=DAT1	B		Wait until close to line change
		C=DAT1	B
		?A=C	P
		GOYES	synchloop
		LA(2)	#1C		Experimental value
-		A=A-1	B
		GONC	-
		LAHEX	0000000000	Experimental - tighter might fail
		D=D+1	X
		GOC	synchfail	Synch failure - error
		A=DAT1	B
		?A#C	P
		GOYES	synchloop

skipsynch	D1=(5)	=TIMER2
		C=0	P
		DAT1=C	P		Clear low nibble to stabilize
		C=DAT1	8
		GOSBVL	=GetTimeEnd
		GOSBVL	=AllowIntr
		RTNNC
		GOVLNG	=FUBAR4		Warmstart - bad time

synchfail	GOSBVL	=AllowIntr
		LC(5)	#D02		"Invalid Time"
		GOVLNG	=GPErrjmpC
    ENDCODE

  ( Program gets evaluated in here )

    CODE
TIMEPART2	GOSBVL	=SAVPTR
		A=0	W		Read current time registers
		C=0	W
		D0=(5)	=G_NEXTIRQ
		D1=(5)	=TIMER2
		A=DAT0	13
		C=DAT1	8
		B=A	W		B[W] = nextirq
		D0=(5)	=G_SAVE_C[A]	C[A] interrupt save variable address
		R3=C	W		R3[W] = ticks
		D=C	A
		A=0	A
		A=A-1	A		Avoid getting a display update
* Even address
-		A=A+1	A		during C=C+1 sequence
		C=DAT1	A		[A] chosen to get a .14 tick loop
		?C=D	X		Experimentally counter = 0 - 6
		GOYES	-		35 cycle loop (30 in the last one)
* Even address
		C=0	W		e 12.25	Set interrupt
		DAT1=C	8		o 24
		C=DAT1	W		o 36
		C=DAT1	W		o 36
* 5 cycle units

		LC(2)	1		And keep increasing C[A]
		LC(2)	2		...
		LC(2)	3
		LC(2)	4		The number of opcodes needed is
		LC(2)	5		dependant on the possible values.
		LC(2)	6		Experimentally the limits are
		LC(2)	7			5 - 28
		LC(2)	8		but we don't really care to optimize
		LC(2)	9
		LC(2)	10
		LC(2)	11
		LC(2)	12
		LC(2)	13
		LC(2)	14		LC(2) is stable in GX so that
		LC(2)	15		it takes equal cycles for even
		LC(2)	16		and odd addresses
		LC(2)	17
		LC(2)	18
		LC(2)	19
		LC(2)	20
		LC(2)	21
		LC(2)	22
		LC(2)	23
		LC(2)	24
		LC(2)	25
		LC(2)	26
		LC(2)	27
		LC(2)	28
		LC(2)	29
		LC(2)	30
		LC(2)	31
		LC(2)	32
		LC(2)	33
		LC(2)	34
		LC(2)	35
		LC(2)	36
		LC(2)	37
		LC(2)	38
		LC(2)	39
		C=DAT0	A		C[A]=interrupt C[A]
		R0=C.F	A		R0[A]=frac
		R1=A.F	A		R1[A]=loops

		C=R3	W		C[W]=ticks
		GOSBVL	=GetTimeEnd	C[W]=time
		R4=C	W		R4[W]=time

		GOSUB	TimGetLam	C[W]=new time
		A=DAT0	13		A[W]=old time
		C=C-A	W		C[W]=difference
		DAT0=C	13		Store back

* Fix time to correct value. Some ticks have been lost in the code above
* and in the generated interrupt - but can't really help it.

		A=R0	A
		R2=A	A		Save R0[A]
		GOSBVL	=DisableIntr
		GOSBVL	=getCLKON	Establish sCLKON
		C=R3	W
		D=C	W		D[W]=ticks
		C=R4	W		C[W]=time
		GOSBVL	=CLKUPD
		GOSBVL	=AllowIntr
		A=R2	A
		R0=A	A
		GOVLNG	=Push2#Loop	Push fractional ticks & dispwaits

    ENDCODE

    1GETLAM #>%					( #frac #waits %ticks )

* The ratio is 7 for the counters

    UNROT SEVEN #* #+

* And the unit is 0.02 ticks

* C[A] is 0.02 ticks in GX

    UNCOERCE % 0.02 %* %-

* The total overhead was measured by using the commented out code
* instead of evaluating the object at label 'OverHead'.
* The measured average from 100 runs was 3.41 ticks
* But - after a speedtest + ON-C the value will be higher until
*	until TurnOff is done. Thus we calibrate by timing a known
*	result instead of a fixed amount each time.

    %0
    CODE
	GOTO	TIMEPART1
    ENDCODE
    CODE
	GOTO	TIMEPART2
    ENDCODE
    ROTDROP 1GETABND #>%			( %ticks1 #frac #waits %ticks )
    UNROT SEVEN #* #+
    UNCOERCE % 0.02 %* %-			( %ticks1 %ticks2 )

* Execution time for %0 should be 0.39 ticks by executing a few hundred
* of them in sequence and taking the average

    % 0.39 %-

* And fix the error

    %-

* Format the result into ":ticks:time"

    DUP % 8.192 %/				( %ticks %seconds )
    %2 RNDXY

  ( Choose units )

    DUP % 100 %>= ITE				( limit = 0.1s )
    :: % 1000 %/ DOEXT %1 tok_s umEND [;] ;
    DOEXT %1 CHR_m tok_s umP umEND [;]
    UM>U					( %ticks unit_seconds )
    SWAP DECOMP$ >TAG				( Tag with %ticks )
  ;

* Copy above subroutine to even address

  CHR_X TOTEMPOB
  CODE
	A=DAT1	A
	?ABIT=1	0
	GOYES	+
+	GOVLNG	=PushT/FLoop
  ENDCODE
  IT TOTEMPOB DROP
  TOTEMPOB

* And evaluate it there

  COLA_EVAL
;

**********************************************************************
* Notes:
*
*	Note that in software terms we are talking about an accuracy
*	of a single C=C+1 A instruction. In reality the hardware errors
*	dominate long runs, somewhat in short runs too. Also note that
*	C=C+1 A was chosen precisely because 50 seems to be the maximum
*	value it can reach during one tick, thus providing nice 0.02 tick
*	scale. 
*
*	Fractional tick gaps due to display updates are not emulated,
*	so in reality there is some error close to even tick boundaries.
*	This could in principle be fixed by using some slower opcode
*	in combination with TIMER2 bit check (display update) but this
*	doesn't seem worth the trouble.
*
**********************************************************************

[ RETURN TO DIRECTORY ]