home *** CD-ROM | disk | FTP | other *** search
/ Graphics Programming Black Book (Special Edition) / BlackBook.bin / disk1 / source / chapterk / pcztnear.asm < prev   
Encoding:
Assembly Source File  |  1997-06-18  |  14.3 KB  |  448 lines

  1. ; ****PCZTNEAR.ASM
  2. ; The C-near-callable version of the precision Zen timer
  3. ;       (PZTIMER.ASM)
  4. ;
  5. ; Note: use NOSMART with TASM (at least version 2.0) to keep
  6. ;       the assembler from turning far calls in the reference
  7. ;       timing code into PUSH CS/near call sequences, thereby
  8. ;       messing up the reference call times. This problem may
  9. ;       arise with other optimizing assemblers as well.
  10. ;
  11. ; Uses the 8253 timer to time the performance of code that takes
  12. ; less than about 54 milliseconds to execute, with a resolution
  13. ; of better than 10 microseconds.
  14. ;
  15. ; By Michael Abrash 4/26/89
  16. ;
  17. ; Externally callable routines:
  18. ;
  19. ;  ZTimerOn: Starts the Zen timer, with interrupts disabled.
  20. ;
  21. ;  ZTimerOff: Stops the Zen timer, saves the timer count,
  22. ;       times the overhead code, and restores interrupts to the
  23. ;       state they were in when ZTimerOn was called.
  24. ;
  25. ;  ZTimerReport: Prints the net time that passed between starting
  26. ;       and stopping the timer.
  27. ;
  28. ; Note: If longer than about 54 ms passes between ZTimerOn and
  29. ;       ZTimerOff calls, the timer turns over and the count is
  30. ;       inaccurate. When this happens, an error message is displayed
  31. ;       instead of a count. The long-period Zen timer should be used
  32. ;       in such cases.
  33. ;
  34. ; Note: Interrupts *MUST* be left off between calls to ZTimerOn
  35. ;       and ZTimerOff for accurate timing and for detection of
  36. ;       timer overflow.
  37. ;
  38. ; Note: These routines can introduce slight inaccuracies into the
  39. ;       system clock count for each code section timed even if
  40. ;       timer 0 doesn't overflow. If timer 0 does overflow, the
  41. ;       system clock can become slow by virtually any amount of
  42. ;       time, since the system clock can't advance while the
  43. ;       precison timer is timing. Consequently, it's a good idea
  44. ;       to reboot at the end of each timing session. (The
  45. ;       battery-backed clock, if any, is not affected by the Zen
  46. ;       timer.)
  47. ;
  48. ; All registers, and all flags except the interrupt flag, are
  49. ; preserved by all routines. Interrupts are enabled and then disabled
  50. ; by ZTimerOn, and are restored by ZTimerOff to the state they were
  51. ; in when ZTimerOn was called.
  52. ;
  53.  
  54. _TEXT   segment word public 'CODE'
  55.         assume  cs:_TEXT, ds:nothing
  56.         public  _ZTimerOn, _ZTimerOff, _ZTimerReport
  57.  
  58. ;
  59. ; Base address of the 8253 timer chip.
  60. ;
  61. BASE_8253               equ     40h
  62. ;
  63. ; The address of the timer 0 count registers in the 8253.
  64. ;
  65. TIMER_0_8253            equ     BASE_8253 + 0
  66. ;
  67. ; The address of the mode register in the 8253.
  68. ;
  69. MODE_8253               equ     BASE_8253 + 3
  70. ;
  71. ; The address of Operation Command Word 3 in the 8259 Programmable
  72. ; Interrupt Controller (PIC) (write only, and writable only when
  73. ; bit 4 of the byte written to this address is 0 and bit 3 is 1).
  74. ;
  75. OCW3                    equ     20h
  76. ;
  77. ; The address of the Interrupt Request register in the 8259 PIC
  78. ; (read only, and readable only when bit 1 of OCW3 = 1 and bit 0
  79. ; of OCW3 = 0).
  80. ;
  81. IRR                     equ     20h
  82. ;
  83. ; Macro to emulate a POPF instruction in order to fix the bug in some
  84. ; 80286 chips which allows interrupts to occur during a POPF even when
  85. ; interrupts remain disabled.
  86. ;
  87. MPOPF macro
  88.         local   p1, p2
  89.         jmp short p2
  90. p1:     iret                    ;jump to pushed address & pop flags
  91. p2:     push    cs              ;construct far return address to
  92.         call    p1              ; the next instruction
  93.         endm
  94.  
  95. ;
  96. ; Macro to delay briefly to ensure that enough time has elapsed
  97. ; between successive I/O accesses so that the device being accessed
  98. ; can respond to both accesses even on a very fast PC.
  99. ;
  100. DELAY   macro
  101.         jmp     $+2
  102.         jmp     $+2
  103.         jmp     $+2
  104.         endm
  105.  
  106. OriginalFlags           db      ?       ;storage for upper byte of
  107.                                         ; FLAGS register when
  108.                                         ; ZTimerOn called
  109. TimedCount              dw      ?       ;timer 0 count when the timer
  110.                                         ; is stopped
  111. ReferenceCount          dw      ?       ;number of counts required to
  112.                                         ; execute timer overhead code
  113. OverflowFlag            db      ?       ;used to indicate whether the
  114.                                         ; timer overflowed during the
  115.                                         ; timing interval
  116. ;
  117. ; String printed to report results.
  118. ;
  119. OutputStr       label   byte
  120.                 db      'Timed count: ', 5 dup (?)
  121. ASCIICountEnd   label   byte
  122.                 db      ' microseconds', 0dh, 0ah
  123.                 db      '$'
  124. ;
  125. ; String printed to report timer overflow.
  126. ;
  127. OverflowStr     label   byte
  128.         db      0dh, 0ah
  129.         db      '****************************************************'
  130.         db      0dh, 0ah
  131.         db      '* The timer overflowed, so the interval timed was  *'
  132.         db      0dh, 0ah
  133.         db      '* too long for the precision timer to measure.     *'
  134.         db      0dh, 0ah
  135.         db      '* Please perform the timing test again with the    *'
  136.         db      0dh, 0ah
  137.         db      '* long-period timer.                               *'
  138.         db      0dh, 0ah
  139.         db      '****************************************************'
  140.         db      0dh, 0ah
  141.         db      '$'
  142.  
  143. ;********************************************************************
  144. ;* Routine called to start timing.                                  *
  145. ;********************************************************************
  146.  
  147. _ZTimerOn       proc    near
  148.  
  149. ;
  150. ; Save the context of the program being timed.
  151. ;
  152.         push    ax
  153.         pushf
  154.         pop     ax                      ;get flags so we can keep
  155.                                         ; interrupts off when leaving
  156.                                         ; this routine
  157.         mov     cs:[OriginalFlags],ah   ;remember the state of the
  158.                                         ; Interrupt flag
  159.         and     ah,0fdh                 ;set pushed interrupt flag
  160.                                         ; to 0
  161.         push    ax
  162. ;
  163. ; Turn on interrupts, so the timer interrupt can occur if it's
  164. ; pending.
  165. ;
  166.         sti
  167. ;
  168. ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
  169. ; linear counting rather than count-by-two counting. Also
  170. ; leaves the 8253 waiting for the initial timer 0 count to
  171. ; be loaded.
  172. ;
  173.         mov     al,00110100b            ;mode 2
  174.         out     MODE_8253,al
  175. ;
  176. ; Set the timer count to 0, so we know we won't get another
  177. ; timer interrupt right away.
  178. ; Note: this introduces an inaccuracy of up to 54 ms in the system
  179. ; clock count each time it is executed.
  180. ;
  181.         DELAY
  182.         sub     al,al
  183.         out     TIMER_0_8253,al         ;lsb
  184.         DELAY
  185.         out     TIMER_0_8253,al         ;msb
  186. ;
  187. ; Wait before clearing interrupts to allow the interrupt generated
  188. ; when switching from mode 3 to mode 2 to be recognized. The delay
  189. ; must be at least 210 ns long to allow time for that interrupt to
  190. ; occur. Here, 10 jumps are used for the delay to ensure that the
  191. ; delay time will be more than long enough even on a very fast PC.
  192. ;
  193.         rept 10
  194.         jmp     $+2
  195.         endm
  196. ;
  197. ; Disable interrupts to get an accurate count.
  198. ;
  199.         cli
  200. ;
  201. ; Set the timer count to 0 again to start the timing interval.
  202. ;
  203.         mov     al,00110100b            ;set up to load initial
  204.         out     MODE_8253,al            ; timer count
  205.         DELAY
  206.         sub     al,al
  207.         out     TIMER_0_8253,al         ;load count lsb
  208.         DELAY
  209.         out     TIMER_0_8253,al         ;load count msb
  210. ;
  211. ; Restore the context and return.
  212. ;
  213.         MPOPF                           ;keeps interrupts off
  214.         pop     ax
  215.         ret
  216.  
  217. _ZTimerOn       endp
  218.  
  219. ;********************************************************************
  220. ;* Routine called to stop timing and get count.                     *
  221. ;********************************************************************
  222.  
  223. _ZTimerOff proc near
  224.  
  225. ;
  226. ; Save the context of the program being timed.
  227. ;
  228.         push    ax
  229.         push    cx
  230.         pushf
  231. ;
  232. ; Latch the count.
  233. ;
  234.         mov     al,00000000b            ;latch timer 0
  235.         out     MODE_8253,al
  236. ;
  237. ; See if the timer has overflowed by checking the 8259 for a pending
  238. ; timer interrupt.
  239. ;
  240.         mov     al,00001010b            ;OCW3, set up to read
  241.         out     OCW3,al                 ; Interrupt Request register
  242.         DELAY
  243.         in      al,IRR                  ;read Interrupt Request
  244.                                         ; register
  245.         and     al,1                    ;set AL to 1 if IRQ0 (the
  246.                                         ; timer interrupt) is pending
  247.         mov     cs:[OverflowFlag],al    ;store the timer overflow
  248.                                         ; status
  249. ;
  250. ; Allow interrupts to happen again.
  251. ;
  252.         sti
  253. ;
  254. ; Read out the count we latched earlier.
  255. ;
  256.         in      al,TIMER_0_8253         ;least significant byte
  257.         DELAY
  258.         mov     ah,al
  259.         in      al,TIMER_0_8253         ;most significant byte
  260.         xchg    ah,al
  261.         neg     ax                      ;convert from countdown
  262.                                         ; remaining to elapsed
  263.                                         ; count
  264.         mov     cs:[TimedCount],ax
  265. ; Time a zero-length code fragment, to get a reference for how
  266. ; much overhead this routine has. Time it 16 times and average it,
  267. ; for accuracy, rounding the result.
  268. ;
  269.         mov     cs:[ReferenceCount],0
  270.         mov     cx,16
  271.         cli                             ;interrupts off to allow a
  272.                                         ; precise reference count
  273. RefLoop:
  274.         call    ReferenceZTimerOn
  275.         call    ReferenceZTimerOff
  276.         loop    RefLoop
  277.         sti
  278.         add     cs:[ReferenceCount],8   ;total + (0.5 * 16)
  279.         mov     cl,4
  280.         shr     cs:[ReferenceCount],cl  ;(total) / 16 + 0.5
  281. ;
  282. ; Restore original interrupt state.
  283. ;
  284.         pop     ax                      ;retrieve flags when called
  285.         mov     ch,cs:[OriginalFlags]   ;get back the original upper
  286.                                         ; byte of the FLAGS register
  287.         and     ch,not 0fdh             ;only care about original
  288.                                         ; interrupt flag...
  289.         and     ah,0fdh                 ;...keep all other flags in
  290.                                         ; their current condition
  291.         or      ah,ch                   ;make flags word with original
  292.                                         ; interrupt flag
  293.         push    ax                      ;prepare flags to be popped
  294. ;
  295. ; Restore the context of the program being timed and return to it.
  296. ;
  297.         MPOPF                           ;restore the flags with the
  298.                                         ; original interrupt state
  299.         pop     cx
  300.         pop     ax
  301.         ret
  302.  
  303. _ZTimerOff endp
  304.  
  305. ;
  306. ; Called by ZTimerOff to start timer for overhead measurements.
  307. ;
  308.  
  309. ReferenceZTimerOn       proc    near
  310. ;
  311. ; Save the context of the program being timed.
  312. ;
  313.         push    ax
  314.         pushf           ;interrupts are already off
  315. ;
  316. ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
  317. ; linear counting rather than count-by-two counting.
  318. ;
  319.         mov     al,00110100b    ;set up to load
  320.         out     MODE_8253,al    ; initial timer count
  321.         DELAY
  322. ;
  323. ; Set the timer count to 0.
  324. ;
  325.         sub     al,al
  326.         out     TIMER_0_8253,al ;load count lsb
  327.         DELAY
  328.         out     TIMER_0_8253,al ;load count msb
  329. ;
  330. ; Restore the context of the program being timed and return to it.
  331. ;
  332.         MPOPF
  333.         pop     ax
  334.         ret
  335.  
  336. ReferenceZTimerOn       endp
  337.  
  338. ;
  339. ; Called by ZTimerOff to stop timer and add result to ReferenceCount
  340. ; for overhead measurements.
  341. ;
  342.  
  343. ReferenceZTimerOff proc near
  344. ;
  345. ; Save the context of the program being timed.
  346. ;
  347.         push    ax
  348.         push    cx
  349.         pushf
  350. ;
  351. ; Latch the count and read it.
  352. ;
  353.         mov     al,00000000b            ;latch timer 0
  354.         out     MODE_8253,al
  355.         DELAY
  356.         in      al,TIMER_0_8253         ;lsb
  357.         DELAY
  358.         mov     ah,al
  359.         in      al,TIMER_0_8253         ;msb
  360.         xchg    ah,al
  361.         neg     ax                      ;convert from countdown
  362.                                         ; remaining to amount
  363.                                         ; counted down
  364.         add     cs:[ReferenceCount],ax
  365. ;
  366. ; Restore the context of the program being timed and return to it.
  367. ;
  368.         MPOPF
  369.         pop     cx
  370.         pop     ax
  371.         ret
  372.  
  373. ReferenceZTimerOff endp
  374.  
  375. ;********************************************************************
  376. ;* Routine called to report timing results.                         *
  377. ;********************************************************************
  378.  
  379. _ZTimerReport   proc    near
  380.  
  381.         pushf
  382.         push    ax
  383.         push    bx
  384.         push    cx
  385.         push    dx
  386.         push    si
  387.         push    ds
  388. ;
  389.         push    cs      ;DOS functions require that DS point
  390.         pop     ds      ; to text to be displayed on the screen
  391.         assume  ds:_TEXT
  392. ;
  393. ; Check for timer 0 overflow.
  394. ;
  395.         cmp     [OverflowFlag],0
  396.         jz      PrintGoodCount
  397.         mov     dx,offset OverflowStr
  398.         mov     ah,9
  399.         int     21h
  400.         jmp     short EndZTimerReport
  401. ;
  402. ; Convert net count to decimal ASCII in microseconds.
  403. ;
  404. PrintGoodCount:
  405.         mov     ax,[TimedCount]
  406.         sub     ax,[ReferenceCount]
  407.         mov     si,offset ASCIICountEnd - 1
  408. ;
  409. ; Convert count to microseconds by multiplying by .8381.
  410. ;
  411.         mov     dx,8381
  412.         mul     dx
  413.         mov     bx,10000
  414.         div     bx              ;* .8381 = * 8381 / 10000
  415. ;
  416. ; Convert time in microseconds to 5 decimal ASCII digits.
  417. ;
  418.         mov     bx,10
  419.         mov     cx,5
  420. CTSLoop:
  421.         sub     dx,dx
  422.         div     bx
  423.         add     dl,'0'
  424.         mov     [si],dl
  425.         dec     si
  426.         loop    CTSLoop
  427. ;
  428. ; Print the results.
  429. ;
  430.         mov     ah,9
  431.         mov     dx,offset OutputStr
  432.         int     21h
  433. ;
  434. EndZTimerReport:
  435.         pop     ds
  436.         pop     si
  437.         pop     dx
  438.         pop     cx
  439.         pop     bx
  440.         pop     ax
  441.         MPOPF
  442.         ret
  443.  
  444. _ZTimerReport   endp
  445.  
  446. _TEXT   ends
  447.         end
  448.