**LISTING 3.1 PZTIMER.ASM** ; The precision Zen timer (PZTIMER.ASM) ; ; Uses the 8253 timer to time the performance of code that takes ; less than about 54 milliseconds to execute, with a resolution ; of better than 10 microseconds. ; ; By Michael Abrash ; ; Externally callable routines: ; ; ZTimerOn: Starts the Zen timer, with interrupts disabled. ; ; ZTimerOff: Stops the Zen timer, saves the timer count, ; times the overhead code, and restores interrupts to the ; state they were in when ZTimerOn was called. ; ; ZTimerReport: Prints the net time that passed between starting ; and stopping the timer. ; ; Note: If longer than about 54 ms passes between ZTimerOn and ; ZTimerOff calls, the timer turns over and the count is ; inaccurate. When this happens, an error message is displayed ; instead of a count. The long-period Zen timer should be used ; in such cases. ; ; Note: Interrupts *MUST* be left off between calls to ZTimerOn ; and ZTimerOff for accurate timing and for detection of ; timer overflow. ; ; Note: These routines can introduce slight inaccuracies into the ; system clock count for each code section timed even if ; timer 0 doesn't overflow. If timer 0 does overflow, the ; system clock can become slow by virtually any amount of ; time, since the system clock can't advance while the ; precison timer is timing. Consequently, it's a good idea ; to reboot at the end of each timing session. (The ; battery-backed clock, if any, is not affected by the Zen ; timer.) ; ; All registers, and all flags except the interrupt flag, are ; preserved by all routines. Interrupts are enabled and then disabled ; by ZTimerOn, and are restored by ZTimerOff to the state they were ; in when ZTimerOn was called. ; Code segment word public ‘CODE' assumecs: Code, ds:nothing public ZTimerOn, ZTimerOff, ZTimerReport ; ; Base address of the 8253 timer chip. ; BASE_8253equ40h ; ; The address of the timer 0 count registers in the 8253. ; TIMER_0_8253 equBASE_8253 + 0 ; ; The address of the mode register in the 8253. ; MODE_8253 equBASE_8253 + 3 ; ; The address of Operation Command Word 3 in the 8259 Programmable ; Interrupt Controller (PIC) (write only, and writable only when ; bit 4 of the byte written to this address is 0 and bit 3 is 1). ; OCW3 equ20h ; ; The address of the Interrupt Request register in the 8259 PIC ; (read only, and readable only when bit 1 of OCW3 = 1 and bit 0 ; of OCW3 = 0). ; IRR equ20h ; ; Macro to emulate a POPF instruction in order to fix the bug in some ; 80286 chips which allows interrupts to occur during a POPF even when ; interrupts remain disabled. ; MPOPF macro local p1, p2 jmp short p2 p1: iret ; jump to pushed address & pop flags p2: push cs ; construct far return address to call p1 ; the next instruction endm ; ; Macro to delay briefly to ensure that enough time has elapsed ; between successive I/O accesses so that the device being accessed ; can respond to both accesses even on a very fast PC. ; DELAY macro jmp $+2 jmp $+2 jmp $+2 endm OriginalFlags db ? ; storage for upper byte of ; FLAGS register when ; ZTimerOn called TimedCount dw ? ; timer 0 count when the timer ; is stopped ReferenceCount dw ; number of counts required to ; execute timer overhead code OverflowFlag db ? ; used to indicate whether the ; timer overflowed during the ; timing interval ; ; String printed to report results. ; OutputStr label byte db 0dh, 0ah, ‘Timed count: ‘, 5 dup (?) ASCIICountEnd labelbyte db ‘ microseconds', 0dh, 0ah db ‘$' ; ; String printed to report timer overflow. ; OverflowStr label byte db 0dh, 0ah db ‘****************************************************' db 0dh, 0ah db ‘* The timer overflowed, so the interval timed was *' db 0dh, 0ah db ‘* too long for the precision timer to measure. *' db 0dh, 0ah db ‘* Please perform the timing test again with the *' db0dh, 0ah db ‘* long-period timer. *' db 0dh, 0ah db ‘****************************************************' db 0dh, 0ah db ‘$' ; ******************************************************************** ; * Routine called to start timing. * ; ******************************************************************** ZTimerOn proc near ; ; Save the context of the program being timed. ; push ax pushf pop ax ; get flags so we can keep ; interrupts off when leaving ; this routine mov cs:[OriginalFlags],ah ; remember the state of the ; Interrupt flag and ah,0fdh ; set pushed interrupt flag ; to 0 push ax ; ; Turn on interrupts, so the timer interrupt can occur if it's ; pending. ; sti ; ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause ; linear counting rather than count-by-two counting. Also ; leaves the 8253 waiting for the initial timer 0 count to ; be loaded. ; mov al,00110100b ;mode 2 out MODE_8253,al ; ; Set the timer count to 0, so we know we won't get another ; timer interrupt right away. ; Note: this introduces an inaccuracy of up to 54 ms in the system ; clock count each time it is executed. ; DELAY sub al,al out TIMER_0_8253,al ;lsb DELAY out TIMER_0_8253,al ;msb ; ; Wait before clearing interrupts to allow the interrupt generated ; when switching from mode 3 to mode 2 to be recognized. The delay ; must be at least 210 ns long to allow time for that interrupt to ; occur. Here, 10 jumps are used for the delay to ensure that the ; delay time will be more than long enough even on a very fast PC. ; rept 10 jmp $+2 endm ; ; Disable interrupts to get an accurate count. ; cli ; ; Set the timer count to 0 again to start the timing interval. ; mov al,00110100b ; set up to load initial out MODE_8253,al ; timer count DELAY sub al,al out TIMER_0_8253,al ; load count lsb DELAY out TIMER_0_8253,al; load count msb ; ; Restore the context and return. ; MPOPF ; keeps interrupts off pop ax ret ZTimerOn endp ;******************************************************************** ;* Routine called to stop timing and get count. * ;******************************************************************** ZTimerOff proc near ; ; Save the context of the program being timed. ; push ax push cx pushf ; ; Latch the count. ; mov al,00000000b ; latch timer 0 out MODE_8253,al ; ; See if the timer has overflowed by checking the 8259 for a pending ; timer interrupt. ; mov al,00001010b ; OCW3, set up to read out OCW3,al; Int errupt Request register DELAY ina l,IRR; read Interrupt Request ; register and al,1 ; set AL to 1 if IRQ0 (the ; timer interrupt) is pending mov cs:[OverflowFlag],al; store the timer overflow ; status ; ; Allow interrupts to happen again. ; sti ; ; Read out the count we latched earlier. ; in al,TIMER_0_8253 ; least significant byte DELAY mov ah,al in al,TIMER_0_8253 ; most significant byte xchg ah,al neg ax ; convert from countdown ; remaining to elapsed ; count mov cs:[TimedCount],ax ; Time a zero-length code fragment, to get a reference for how ; much overhead this routine has. Time it 16 times and average it, ; for accuracy, rounding the result. ; mov cs:[ReferenceCount],0 mov cx,16 cli ; interrupts off to allow a ; precise reference count RefLoop: call ReferenceZTimerOn call ReferenceZTimerOff loop RefLoop sti add cs:[ReferenceCount],8; total + (0.5 * 16) mov cl,4 shr cs:[ReferenceCount],cl; (total) / 16 + 0.5 ; ; Restore original interrupt state. ; pop ax ; retrieve flags when called mov ch,cs:[OriginalFlags] ; get back the original upper ; byte of the FLAGS register and ch,not 0fdh ; only care about original ; interrupt flag... and ah,0fdh ; ...keep all other flags in ; their current condition or ah,ch ; make flags word with original ; interrupt flag push ax ; prepare flags to be popped ; ; Restore the context of the program being timed and return to it. ; MPOPF ; restore the flags with the ; original interrupt state pop cx pop ax ret ZTimerOff endp ; ; Called by ZTimerOff to start timer for overhead measurements. ; ReferenceZTimerOnproc near ; ; Save the context of the program being timed. ; push ax pushf ; interrupts are already off ; ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause ; linear counting rather than count-by-two counting. ; mov al,00110100b ; set up to load out MODE_8253,al ; initial timer count DELAY ; ; Set the timer count to 0. ; sub al,al out TIMER_0_8253,al; load count lsb DELAY out TIMER_0_8253,al; load count msb ; ; Restore the context of the program being timed and return to it. ; MPOPF pop ax ret ReferenceZTimerOnendp ; ; Called by ZTimerOff to stop timer and add result to ReferenceCount ; for overhead measurements. ; ReferenceZTimerOff proc near ; ; Save the context of the program being timed. ; push ax push cx pushf ; ; Latch the count and read it. ; mov al,00000000b ; latch timer 0 out MODE_8253,al DELAY in al,TIMER_0_8253 ; lsb DELAY mov ah,al in al,TIMER_0_8253 ; msb xchg ah,al neg ax ; convert from countdown ; remaining to amount ; counted down add cs:[ReferenceCount],ax ; ; Restore the context of the program being timed and return to it. ; MPOPF pop cx pop ax ret ReferenceZTimerOff endp ; ******************************************************************** ; * Routine called to report timing results. * ; ******************************************************************** ZTimerReport procnear pushf push ax push bx push cx push dx push si push ds ; push cs ; DOS functions require that DS point pop ds ; to text to be displayed on the screen assume ds :Code ; ; Check for timer 0 overflow. ; cmp [OverflowFlag],0 jz PrintGoodCount mov dx,offset OverflowStr mov ah,9 int 21h jmp short EndZTimerReport ; ; Convert net count to decimal ASCII in microseconds. ; PrintGoodCount: mov ax,[TimedCount] sub ax,[ReferenceCount] mov si,offset ASCIICountEnd - 1 ; ; Convert count to microseconds by multiplying by .8381. ; mov dx, 8381 mul dx mov bx, 10000 div bx ;* .8381 = * 8381 / 10000 ; ; Convert time in microseconds to 5 decimal ASCII digits. ; mov bx, 10 mov cx, 5 CTSLoop: sub dx, dx div bx add dl,'0' mov [si],dl dec si loop CTSLoop ; ; Print the results. ; mov ah, 9 mov dx, offset OutputStr int 21h ; EndZTimerReport: pop ds pop si pop dx pop cx pop bx pop ax MPOPF ret ZTimerReport endp Code ends end