home *** CD-ROM | disk | FTP | other *** search
/ Graphics Programming Black Book (Special Edition) / BlackBook.bin / disk1 / source / chapter3-p1 / pztimer.asm < prev    next >
Assembly Source File  |  1997-06-18  |  11KB  |  443 lines

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