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