home *** CD-ROM | disk | FTP | other *** search
/ Graphics Programming Black Book (Special Edition) / BlackBook.bin / disk1 / source / chapterk / pcltnear.asm next >
Encoding:
Assembly Source File  |  1997-06-18  |  16.6 KB  |  639 lines

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