home *** CD-ROM | disk | FTP | other *** search
/ Graphics Programming Black Book (Special Edition) / BlackBook.bin / disk1 / zoa / zen_list.exe / LZTIMER.ASM < prev    next >
Assembly Source File  |  1990-02-15  |  17KB  |  637 lines

  1. ;
  2. ; *** Listing 2-5 ***
  3. ;
  4. ; The long-period Zen timer. (LZTIMER.ASM)
  5. ; Uses the 8253 timer and the BIOS time-of-day count to time the
  6. ; performance of code that takes less than an hour to execute.
  7. ; Because interrupts are left on (in order to allow the timer
  8. ; interrupt to be recognized), this is less accurate than the
  9. ; precision Zen timer, so it is best used only to time code that takes
  10. ; more than about 54 milliseconds to execute (code that the precision
  11. ; Zen timer reports overflow on). Resolution is limited by the
  12. ; occurrence of timer interrupts.
  13. ;
  14. ; By Michael Abrash 4/26/89
  15. ;
  16. ; Externally callable routines:
  17. ;
  18. ;  ZTimerOn: Saves the BIOS time of day count and starts the
  19. ;    long-period Zen timer.
  20. ;
  21. ;  ZTimerOff: Stops the long-period Zen timer and saves the timer
  22. ;    count and the BIOS time-of-day count.
  23. ;
  24. ;  ZTimerReport: Prints the time that passed between starting and
  25. ;    stopping the timer.
  26. ;
  27. ; Note: If either more than an hour passes or midnight falls between
  28. ;    calls to ZTimerOn and ZTimerOff, an error is reported. For
  29. ;    timing code that takes more than a few minutes to execute,
  30. ;    either the DOS TIME command in a batch file before and after
  31. ;    execution of the code to time or the use of the DOS
  32. ;    time-of-day function in place of the long-period Zen timer is
  33. ;    more than adequate.
  34. ;
  35. ; Note: The PS/2 version is assembled by setting the symbol PS2 to 1.
  36. ;    PS2 must be set to 1 on PS/2 computers because the PS/2's
  37. ;    timers are not compatible with an undocumented timer-stopping
  38. ;    feature of the 8253; the alternative timing approach that
  39. ;    must be used on PS/2 computers leaves a short window
  40. ;    during which the timer 0 count and the BIOS timer count may
  41. ;    not be synchronized. You should also set the PS2 symbol to
  42. ;    1 if you're getting erratic or obviously incorrect results.
  43. ;
  44. ; Note: When PS2 is 0, the code relies on an undocumented 8253
  45. ;    feature to get more reliable readings. It is possible that
  46. ;    the 8253 (or whatever chip is emulating the 8253) may be put
  47. ;    into an undefined or incorrect state when this feature is
  48. ;    used.
  49. ;
  50. ;     ***************************************************************
  51. ;     * If your computer displays any hint of erratic behavior        *
  52. ;     *    after the long-period Zen timer is used, such as the floppy *
  53. ;     *    drive failing to operate properly, reboot the system, set   *
  54. ;     *    PS2 to 1 and leave it that way!                    *
  55. ;     ***************************************************************
  56. ;
  57. ; Note: Each block of code being timed should ideally be run several
  58. ;    times, with at least two similar readings required to
  59. ;    establish a true measurement, in order to eliminate any
  60. ;    variability caused by interrupts.
  61. ;
  62. ; Note: Interrupts must not be disabled for more than 54 ms at a
  63. ;    stretch during the timing interval. Because interrupts
  64. ;    are enabled, keys, mice, and other devices that generate
  65. ;    interrupts should not be used during the timing interval.
  66. ;
  67. ; Note: Any extra code running off the timer interrupt (such as
  68. ;    some memory-resident utilities) will increase the time
  69. ;    measured by the Zen timer.
  70. ;
  71. ; Note: These routines can introduce inaccuracies of up to a few
  72. ;    tenths of a second into the system clock count for each
  73. ;    code section timed. Consequently, it's a good idea to
  74. ;    reboot at the conclusion of timing sessions. (The
  75. ;    battery-backed clock, if any, is not affected by the Zen
  76. ;    timer.)
  77. ;
  78. ; All registers and all flags are preserved by all routines.
  79. ;
  80.  
  81. Code    segment word public 'CODE'
  82.     assume    cs:Code, ds:nothing
  83.     public    ZTimerOn, ZTimerOff, ZTimerReport
  84.  
  85. ;
  86. ; Set PS2 to 0 to assemble for use on a fully 8253-compatible
  87. ; system; when PS2 is 0, the readings are more reliable if the
  88. ; computer supports the undocumented timer-stopping feature,
  89. ; but may be badly off if that feature is not supported. In
  90. ; fact, timer-stopping may interfere with your computer's
  91. ; overall operation by putting the 8253 into an undefined or
  92. ; incorrect state.  Use with caution!!!
  93. ;
  94. ; Set PS2 to 1 to assemble for use on non-8253-compatible
  95. ; systems, including PS/2 computers; when PS2 is 1, readings
  96. ; may occasionally be off by 54 ms, but the code will work
  97. ; properly on all systems.
  98. ;
  99. ; A setting of 1 is safer and will work on more systems,
  100. ; while a setting of 0 produces more reliable results in systems
  101. ; which support the undocumented timer-stopping feature of the
  102. ; 8253. The choice is yours.
  103. ;
  104. PS2    equ    1
  105. ;
  106. ; Base address of the 8253 timer chip.
  107. ;
  108. BASE_8253        equ    40h
  109. ;
  110. ; The address of the timer 0 count registers in the 8253.
  111. ;
  112. TIMER_0_8253        equ    BASE_8253 + 0
  113. ;
  114. ; The address of the mode register in the 8253.
  115. ;
  116. MODE_8253        equ    BASE_8253 + 3
  117. ;
  118. ; The address of the BIOS timer count variable in the BIOS
  119. ; data segment.
  120. ;
  121. TIMER_COUNT        equ    46ch
  122. ;
  123. ; Macro to emulate a POPF instruction in order to fix the bug in some
  124. ; 80286 chips which allows interrupts to occur during a POPF even when
  125. ; interrupts remain disabled.
  126. ;
  127. MPOPF macro 
  128.     local    p1, p2
  129.     jmp short p2
  130. p1:    iret            ;jump to pushed address & pop flags
  131. p2:    push    cs        ;construct far return address to
  132.     call    p1        ; the next instruction
  133.     endm
  134.  
  135. ;
  136. ; Macro to delay briefly to ensure that enough time has elapsed
  137. ; between successive I/O accesses so that the device being accessed
  138. ; can respond to both accesses even on a very fast PC.
  139. ;
  140. DELAY    macro
  141.     jmp    $+2
  142.     jmp    $+2
  143.     jmp    $+2
  144.     endm
  145.  
  146. StartBIOSCountLow    dw    ?    ;BIOS count low word at the
  147.                     ; start of the timing period
  148. StartBIOSCountHigh    dw    ?    ;BIOS count high word at the
  149.                     ; start of the timing period
  150. EndBIOSCountLow        dw    ?    ;BIOS count low word at the
  151.                     ; end of the timing period
  152. EndBIOSCountHigh    dw    ?    ;BIOS count high word at the
  153.                     ; end of the timing period
  154. EndTimedCount        dw    ?    ;timer 0 count at the end of
  155.                     ; the timing period
  156. ReferenceCount        dw    ?    ;number of counts required to
  157.                     ; execute timer overhead code
  158. ;
  159. ; String printed to report results.
  160. ;
  161. OutputStr    label    byte
  162.         db    0dh, 0ah, 'Timed count: '
  163. TimedCountStr    db    10 dup (?)
  164.         db    ' microseconds', 0dh, 0ah
  165.         db    '$'
  166. ;
  167. ; Temporary storage for timed count as it's divided down by powers
  168. ; of ten when converting from doubleword binary to ASCII.
  169. ;
  170. CurrentCountLow        dw    ?
  171. CurrentCountHigh    dw    ?
  172. ;
  173. ; Powers of ten table used to perform division by 10 when doing
  174. ; doubleword conversion from binary to ASCII.
  175. ;
  176. PowersOfTen    label    word
  177.     dd    1
  178.     dd    10
  179.     dd    100
  180.     dd    1000
  181.     dd    10000
  182.     dd    100000
  183.     dd    1000000
  184.     dd    10000000
  185.     dd    100000000
  186.     dd    1000000000
  187. PowersOfTenEnd    label    word
  188. ;
  189. ; String printed to report that the high word of the BIOS count
  190. ; changed while timing (an hour elapsed or midnight was crossed),
  191. ; and so the count is invalid and the test needs to be rerun.
  192. ;
  193. TurnOverStr    label    byte
  194.     db    0dh, 0ah
  195.     db    '****************************************************'
  196.     db    0dh, 0ah
  197.     db    '* Either midnight passed or an hour or more passed *'
  198.     db    0dh, 0ah
  199.     db    '* while timing was in progress. If the former was  *'
  200.     db    0dh, 0ah
  201.     db    '* the case, please rerun the test; if the latter   *'
  202.     db    0dh, 0ah
  203.     db    '* was the case, the test code takes too long to    *'
  204.     db    0dh, 0ah
  205.     db    '* run to be timed by the long-period Zen timer.    *'
  206.     db    0dh, 0ah
  207.     db    '* Suggestions: use the DOS TIME command, the DOS   *'
  208.     db    0dh, 0ah
  209.     db    '* time function, or a watch.                       *'
  210.     db    0dh, 0ah
  211.     db    '****************************************************'
  212.     db    0dh, 0ah
  213.     db    '$'
  214.  
  215. ;********************************************************************
  216. ;* Routine called to start timing.                    *
  217. ;********************************************************************
  218.  
  219. ZTimerOn    proc    near
  220.  
  221. ;
  222. ; Save the context of the program being timed.
  223. ;
  224.     push    ax
  225.     pushf
  226. ;
  227. ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
  228. ; linear counting rather than count-by-two counting. Also stops
  229. ; timer 0 until the timer count is loaded, except on PS/2
  230. ; computers.
  231. ;
  232.     mov    al,00110100b    ;mode 2
  233.     out    MODE_8253,al
  234. ;
  235. ; Set the timer count to 0, so we know we won't get another
  236. ; timer interrupt right away.
  237. ; Note: this introduces an inaccuracy of up to 54 ms in the system
  238. ; clock count each time it is executed.
  239. ;
  240.     DELAY
  241.     sub    al,al
  242.     out    TIMER_0_8253,al        ;lsb
  243.     DELAY
  244.     out    TIMER_0_8253,al        ;msb
  245. ;
  246. ; In case interrupts are disabled, enable interrupts briefly to allow
  247. ; the interrupt generated when switching from mode 3 to mode 2 to be
  248. ; recognized. Interrupts must be enabled for at least 210 ns to allow
  249. ; time for that interrupt to occur. Here, 10 jumps are used for the
  250. ; delay to ensure that the delay time will be more than long enough
  251. ; even on a very fast PC.
  252. ;
  253.     pushf
  254.     sti
  255.     rept 10
  256.     jmp    $+2
  257.     endm
  258.     MPOPF
  259. ;
  260. ; Store the timing start BIOS count.
  261. ; (Since the timer count was just set to 0, the BIOS count will
  262. ; stay the same for the next 54 ms, so we don't need to disable
  263. ; interrupts in order to avoid getting a half-changed count.)
  264. ;
  265.     push    ds
  266.     sub    ax,ax
  267.     mov    ds,ax
  268.     mov    ax,ds:[TIMER_COUNT+2]
  269.     mov    cs:[StartBIOSCountHigh],ax
  270.     mov    ax,ds:[TIMER_COUNT]
  271.     mov    cs:[StartBIOSCountLow],ax
  272.     pop    ds
  273. ;
  274. ; Set the timer count to 0 again to start the timing interval.
  275. ;
  276.     mov    al,00110100b        ;set up to load initial
  277.     out    MODE_8253,al        ; timer count
  278.     DELAY
  279.     sub    al,al
  280.     out    TIMER_0_8253,al        ;load count lsb
  281.     DELAY
  282.     out    TIMER_0_8253,al        ;load count msb
  283. ;
  284. ; Restore the context of the program being timed and return to it.
  285. ;
  286.     MPOPF
  287.     pop    ax
  288.     ret
  289.  
  290. ZTimerOn    endp
  291.  
  292. ;********************************************************************
  293. ;* Routine called to stop timing and get count.                *
  294. ;********************************************************************
  295.  
  296. ZTimerOff proc    near
  297.  
  298. ;
  299. ; Save the context of the program being timed.
  300. ;
  301.     pushf
  302.     push    ax
  303.     push    cx
  304. ;
  305. ; In case interrupts are disabled, enable interrupts briefly to allow
  306. ; any pending timer interrupt to be handled. Interrupts must be
  307. ; enabled for at least 210 ns to allow time for that interrupt to
  308. ; occur. Here, 10 jumps are used for the delay to ensure that the
  309. ; delay time will be more than long enough even on a very fast PC.
  310. ;
  311.     sti
  312.     rept    10
  313.     jmp    $+2
  314.     endm
  315.  
  316. ;
  317. ; Latch the timer count.
  318. ;
  319.  
  320. if PS2
  321.  
  322.     mov    al,00000000b
  323.     out    MODE_8253,al        ;latch timer 0 count
  324. ;
  325. ; This is where a one-instruction-long window exists on the PS/2.
  326. ; The timer count and the BIOS count can lose synchronization;
  327. ; since the timer keeps counting after it's latched, it can turn
  328. ; over right after it's latched and cause the BIOS count to turn
  329. ; over before interrupts are disabled, leaving us with the timer
  330. ; count from before the timer turned over coupled with the BIOS
  331. ; count from after the timer turned over. The result is a count
  332. ; that's 54 ms too long.
  333. ;
  334.                 
  335. else
  336.  
  337. ;
  338. ; Set timer 0 to mode 2 (divide-by-N), waiting for a 2-byte count
  339. ; load, which stops timer 0 until the count is loaded. (Only works
  340. ; on fully 8253-compatible chips.)
  341. ;
  342.     mov    al,00110100b        ;mode 2
  343.     out    MODE_8253,al
  344.     DELAY
  345.     mov    al,00000000b        ;latch timer 0 count
  346.     out    MODE_8253,al
  347.  
  348. endif
  349.  
  350.     cli                ;stop the BIOS count
  351. ;
  352. ; Read the BIOS count. (Since interrupts are disabled, the BIOS
  353. ; count won't change.)
  354. ;
  355.     push    ds
  356.     sub    ax,ax
  357.     mov    ds,ax
  358.     mov    ax,ds:[TIMER_COUNT+2]
  359.     mov    cs:[EndBIOSCountHigh],ax
  360.     mov    ax,ds:[TIMER_COUNT]
  361.     mov    cs:[EndBIOSCountLow],ax
  362.     pop    ds
  363. ;
  364. ; Read the timer count and save it.
  365. ;
  366.     in    al,TIMER_0_8253        ;lsb
  367.     DELAY
  368.     mov    ah,al
  369.     in    al,TIMER_0_8253        ;msb
  370.     xchg    ah,al
  371.     neg    ax            ;convert from countdown
  372.                     ; remaining to elapsed
  373.                     ; count
  374.     mov    cs:[EndTimedCount],ax
  375. ;
  376. ; Restart timer 0, which is still waiting for an initial count
  377. ; to be loaded.
  378. ;
  379.  
  380. ife PS2
  381.  
  382.     DELAY
  383.     mov    al,00110100b        ;mode 2, waiting to load a
  384.                     ; 2-byte count
  385.     out    MODE_8253,al
  386.     DELAY
  387.     sub    al,al
  388.     out    TIMER_0_8253,al        ;lsb
  389.     DELAY
  390.     mov    al,ah
  391.     out    TIMER_0_8253,al        ;msb
  392.     DELAY
  393.  
  394. endif
  395.  
  396.     sti        ;let the BIOS count continue
  397. ;
  398. ; Time a zero-length code fragment, to get a reference for how
  399. ; much overhead this routine has. Time it 16 times and average it,
  400. ; for accuracy, rounding the result.
  401. ;
  402.     mov    cs:[ReferenceCount],0
  403.     mov    cx,16
  404.     cli                ;interrupts off to allow a
  405.                     ; precise reference count
  406. RefLoop:
  407.     call    ReferenceZTimerOn
  408.     call    ReferenceZTimerOff
  409.     loop    RefLoop
  410.     sti
  411.     add    cs:[ReferenceCount],8    ;total + (0.5 * 16)
  412.     mov    cl,4
  413.     shr    cs:[ReferenceCount],cl    ;(total) / 16 + 0.5
  414. ;
  415. ; Restore the context of the program being timed and return to it.
  416. ;
  417.     pop    cx
  418.     pop    ax
  419.     MPOPF
  420.     ret
  421.  
  422. ZTimerOff endp
  423.  
  424. ;
  425. ; Called by ZTimerOff to start the timer for overhead measurements.
  426. ;
  427.  
  428. ReferenceZTimerOn    proc    near
  429. ;
  430. ; Save the context of the program being timed.
  431. ;
  432.     push    ax
  433.     pushf
  434. ;
  435. ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
  436. ; linear counting rather than count-by-two counting.
  437. ;
  438.     mov    al,00110100b    ;mode 2
  439.     out    MODE_8253,al
  440. ;
  441. ; Set the timer count to 0.
  442. ;
  443.     DELAY
  444.     sub    al,al
  445.     out    TIMER_0_8253,al        ;lsb
  446.     DELAY
  447.     out    TIMER_0_8253,al        ;msb
  448. ;
  449. ; Restore the context of the program being timed and return to it.
  450. ;
  451.     MPOPF
  452.     pop    ax
  453.     ret
  454.  
  455. ReferenceZTimerOn    endp
  456.  
  457. ;
  458. ; Called by ZTimerOff to stop the timer and add the result to
  459. ; ReferenceCount for overhead measurements. Doesn't need to look
  460. ; at the BIOS count because timing a zero-length code fragment
  461. ; isn't going to take anywhere near 54 ms.
  462. ;
  463.  
  464. ReferenceZTimerOff proc    near
  465. ;
  466. ; Save the context of the program being timed.
  467. ;
  468.     pushf
  469.     push    ax
  470.     push    cx
  471.  
  472. ;
  473. ; Match the interrupt-window delay in ZTimerOff.
  474. ;
  475.     sti
  476.     rept    10
  477.     jmp    $+2
  478.     endm
  479.  
  480.     mov    al,00000000b
  481.     out    MODE_8253,al        ;latch timer 
  482. ;
  483. ; Read the count and save it.
  484. ;
  485.     DELAY
  486.     in    al,TIMER_0_8253        ;lsb
  487.     DELAY
  488.     mov    ah,al
  489.     in    al,TIMER_0_8253        ;msb
  490.     xchg    ah,al
  491.     neg    ax            ;convert from countdown
  492.                     ; remaining to elapsed
  493.                     ; count
  494.     add    cs:[ReferenceCount],ax
  495. ;
  496. ; Restore the context and return.
  497. ;
  498.     pop    cx
  499.     pop    ax
  500.     MPOPF
  501.     ret
  502.  
  503. ReferenceZTimerOff endp
  504.  
  505. ;********************************************************************
  506. ;* Routine called to report timing results.                *
  507. ;********************************************************************
  508.  
  509. ZTimerReport    proc    near
  510.  
  511.     pushf
  512.     push    ax
  513.     push    bx
  514.     push    cx
  515.     push    dx
  516.     push    si
  517.     push    di
  518.     push    ds
  519. ;
  520.     push    cs    ;DOS functions require that DS point
  521.     pop    ds    ; to text to be displayed on the screen
  522.     assume    ds:Code
  523. ;
  524. ; See if midnight or more than an hour passed during timing. If so,
  525. ; notify the user.
  526. ;
  527.     mov    ax,[StartBIOSCountHigh]
  528.     cmp    ax,[EndBIOSCountHigh]
  529.     jz    CalcBIOSTime        ;hour count didn't change,
  530.                     ; so everything's fine
  531.     inc    ax
  532.     cmp    ax,[EndBIOSCountHigh]
  533.     jnz    TestTooLong        ;midnight or two hour
  534.                     ; boundaries passed, so the
  535.                     ; results are no good
  536.     mov    ax,[EndBIOSCountLow]
  537.     cmp    ax,[StartBIOSCountLow]
  538.     jb    CalcBIOSTime        ;a single hour boundary
  539.                     ; passed-that's OK, so long as
  540.                     ; the total time wasn't more
  541.                     ; than an hour
  542.  
  543. ;
  544. ; Over an hour elapsed or midnight passed during timing, which
  545. ; renders the results invalid. Notify the user. This misses the
  546. ; case where a multiple of 24 hours has passed, but we'll rely
  547. ; on the perspicacity of the user to detect that case.
  548. ;
  549. TestTooLong:
  550.     mov    ah,9
  551.     mov    dx,offset TurnOverStr
  552.     int    21h
  553.     jmp    short ZTimerReportDone
  554. ;
  555. ; Convert the BIOS time to microseconds.
  556. ;
  557. CalcBIOSTime:
  558.     mov    ax,[EndBIOSCountLow]
  559.     sub    ax,[StartBIOSCountLow]
  560.     mov    dx,54925        ;number of microseconds each
  561.                     ; BIOS count represents
  562.     mul    dx
  563.     mov    bx,ax            ;set aside BIOS count in
  564.     mov    cx,dx            ; microseconds
  565. ;
  566. ; Convert timer count to microseconds.
  567. ;
  568.     mov    ax,[EndTimedCount]
  569.     mov    si,8381
  570.     mul    si
  571.     mov    si,10000
  572.     div    si        ;* .8381 = * 8381 / 10000
  573. ;
  574. ; Add timer and BIOS counts together to get an overall time in
  575. ; microseconds.
  576. ;
  577.     add    bx,ax
  578.     adc    cx,0
  579. ;
  580. ; Subtract the timer overhead and save the result.
  581. ;
  582.     mov    ax,[ReferenceCount]
  583.     mov    si,8381        ;convert the reference count
  584.     mul    si        ; to microseconds
  585.     mov    si,10000
  586.     div    si        ;* .8381 = * 8381 / 10000
  587.     sub    bx,ax
  588.     sbb    cx,0
  589.     mov    [CurrentCountLow],bx
  590.     mov    [CurrentCountHigh],cx
  591. ;
  592. ; Convert the result to an ASCII string by trial subtractions of
  593. ; powers of 10.
  594. ;
  595.     mov    di,offset PowersOfTenEnd - offset PowersOfTen - 4
  596.     mov    si,offset TimedCountStr
  597. CTSNextDigit:
  598.     mov    bl,'0'
  599. CTSLoop:
  600.     mov    ax,[CurrentCountLow]
  601.     mov    dx,[CurrentCountHigh]
  602.     sub    ax,PowersOfTen[di]
  603.     sbb    dx,PowersOfTen[di+2]
  604.     jc    CTSNextPowerDown
  605.     inc    bl
  606.     mov    [CurrentCountLow],ax
  607.     mov    [CurrentCountHigh],dx
  608.     jmp    CTSLoop
  609. CTSNextPowerDown:
  610.     mov    [si],bl
  611.     inc    si
  612.     sub    di,4
  613.     jns    CTSNextDigit
  614. ;
  615. ;
  616. ; Print the results.
  617. ;
  618.     mov    ah,9
  619.     mov    dx,offset OutputStr
  620.     int    21h
  621. ;
  622. ZTimerReportDone:
  623.     pop    ds
  624.     pop    di
  625.     pop    si
  626.     pop    dx
  627.     pop    cx
  628.     pop    bx
  629.     pop    ax
  630.     MPOPF
  631.     ret
  632.  
  633. ZTimerReport    endp
  634.  
  635. Code    ends
  636.     end
  637.