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