|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T c
Length: 21616 (0x5470) Types: TextFile Names: »cpuid.asm«
└─⟦9ae75bfbd⟧ Bits:30007242 EUUGD3: Starter Kit └─⟦this⟧ »EurOpenD3/network/ka9q/cpuid.asm«
Keywords: NEC V20/V30, software detection? Lines: 687 Summary: CPUID.COM with source and object! I have had enough requests for the CPUID program that it seems to be worth posting. This is a program that can detect the type of processor running on an IBM PC or compatible. It can distinguish among the 8088, 8086, 80188, 80186, V20, V30, and 80286, as well as identify the presence of the numeric coprocessor. Following this introductory note are two items: the MASM source code and the uuencoded binary for CPUID.COM. The source code is not necessary if you just want to run the program, but it is well commented and explains the tricks used to identify the processor type. I didn't write the program, I only pulled it off the net myself. Enjoy. Peter Fales UUCP: ...ihnp4!ihlpe!psfales work: (312) 979-7784 AT&T Information Systems, IW 1Z-243 1100 E. Warrenville Rd., IL 60566 ==========================cut here======================================= title CPUID - Determine CPU & NDP Type page 58,122 name CPUID ; :ts=8 /* for MY editor only */ ; ; CPUID uniquely identifies each NEC & Intel CPU & NDP. ; ; Notes on program structure: ; ; This program uses four segments, two classes, and one group. ; It demonstrates a useful technique for programmers who generate ; .COM programs. In particular, it shows how to use segment ; classes to re-order segments, and how to eliminate the linker's ; warning message about the absence of a stack segment. ; ; The correspondence between segments and classes is as follows: ; ; Segment Class ; ------- ----- ; STACK prog ; DATA data ; MDATA data ; CODE prog ; ; The segments apprear in the above order in the program source ; to avoid forward references in the CODE segment to labels in ; the DATA/MDATA segments. However, because the STACK segment ; appears first in the file, it and all segments in the same ; class are made contiguous by the linker. Thus they precede ; the DATA/MDATA segments in the resulting .COM file because ; the latter are in a different class. In this manner, although ; DATA and MDATA precede CODE in the source file, their order ; is swapped in the .COM file. That way there is no need for ; an initial skip over the data areas to get to the CODE ; segment. As a side benefit, declaring a STACK segment (as ; the first segment in the source) also eliminates the linker's ; warning about that segment missing. Finally, all segments ; are declared to be in the same group so the linker can properly ; resolve offsets. ; ; Note that if you re-assemble the code for any reason, it is ; important to use an assembler later than the IBM version 1.0. ; That version has a number of bugs including an annoying habit ; of alphabetizing segment names in the .OBJ file. If you use ; IBM MASM 2.0, be sure to specify /S to order the segments ; properly. ; ; If the program reports results at variance with your knowledge ; of the system, please contact the author. ; ; Environments tested in: ; ; CPU Speed ; System in MHz CPU NDP ; ------ --------- --- --- ; IBM PC AT 6 Intel 80286 Intel 80287 ; IBM PC AT 9 Intel 80286 Intel 80287 ; IBM PC AT 6 Intel 80286 none ; IBM PC AT 8.5 Intel 80286 none ; IBM PC 4.77 Intel 8088 Intel 8087-3 ; IBM PC 4.77 Intel 8088* Intel 8087-3 ; IBM PC XT 4.77 Intel 8088 none ; IBM PC XT 4.77 Intel 8088 Intel 8087-3 ; IBM PC Portable 4.77 NEC V20 none ; COMPAQ 4.77 Intel 8088 none ; COMPAQ 4.77 NEC V20 none ; AT&T PC 6300 8 Intel 8086 Intel 8087-2 ; AT&T PC 6300 8 NEC V30 Intel 8087-2 ; Tandy 2000 8 Intel 80186 none ; ; * = faulty CPU ; ; Program structure: ; ; Group PGROUP: ; Stack segment STACK, byte-aligned, stack, class 'prog' ; Program segment CODE, byte-aligned, public, class 'prog' ; Data segment DATA, byte-aligned, public, class 'data' ; Data segment MDATA, byte-aligned, public, class 'data' ; ; Assembly requirements: ; ; Use MASM 1.25 or later. ; With IBM's MASM 2.0 only, use /S to avoid alphabetizing the ; segment names. ; Use /r option to generate real NDP code. ; ; MASM CPUID/r; to convert .ASM to .OBJ ; LINK CPUID; to convert .OBJ to .EXE ; EXE2BIN CPUID CPUID.COM to convert .EXE to .COM ; ERASE CPUID.EXE to avoid executing .EXE ; ; Note that the linker doesn't warn about a missing stack segment. ; ; Author: ; ; Original code by: Bob Smith May 1985 ; Qualitas, Inc. ; 8314 Thoreau Dr. ; Bethesda, MD 20817 ; ; Arthur Zachai suggested the technique to distinguish within the ; 808x and 8018x families by exploiting the difference in the ; length of their pre-fetch instruction queues. ; ; Published in PC Tech Journal - April 1986 - Vol 4 No 4 subttl Structures, Records, Equates, & Macros page ARG_STR struc dw ? ; caller's bp ARG_OFF dw ? ; caller's offset ARG_SEG dw ? ; segment ARG_FLG dw ? ; flags ARG_STR ends ; Record to define bits in the CPU's & NDP's flags' registers CPUFLAGS record RO:1,NT:1,IOPL:2,OF:1,DF:1,IF:1,TF:1,SF:1,ZF:1,R1:1,AF:1,R2:1,PF:1,R3:1,CF:1 NDPFLAGS record R4:3,IC:1,RC:2,PC:2,IEM:1,R5:1,PM:1,UM:1,OM:1,ZM:1,DM:1,IM:1 ; FLG_PIQL Pre-fetch instruction queue length, 0 => 4-byte ; 1 => 6-byte ; FLG_08 Intel 808x ; FLG_NEC NEC V20 or V30 ; FLG_18 Intel 8018x ; FLG_28 Intel 8028x ; FLG_87 Intel 8087 ; FLG_287 Intel 80287 ; ; FLG_CERR Faulty CPU ; FLG_NERR Faulty NDP switch setting FLG record RSVD:9,FLG_NERR:1,FLG_CERR:1,FLG_NDP:2,FLG_CPU:3 ; CPU-related flags FLG_PIQL equ 001b shl FLG_CPU FLG_08 equ 000b shl FLG_CPU FLG_NEC equ 010b shl FLG_CPU FLG_18 equ 100b shl FLG_CPU FLG_28 equ 110b shl FLG_CPU FLG_8088 equ FLG_08 FLG_8086 equ FLG_08 or FLG_PIQL FLG_V20 equ FLG_NEC FLG_v30 equ FLG_NEC or FLG_PIQL FLG_80188 equ FLG_18 FLG_80186 equ FLG_18 or FLG_PIQL FLG_80286 equ FLG_28 or FLG_PIQL ; NDP-related flags ; 00b shl FLG_NDP Not Present FLG_87 equ 01b shl FLG_NDP FLG_287 equ 10b shl FLG_NDP BEL equ 07h LF equ 0ah CR equ 0dh EOS equ '$' POPFF macro local L1,L2 jmp short L2 ; skip over IRET L1: iret ; pop the cs & ip pushed below along ; with the flags, our original purpose L2: push cs ; prepare for IRET by pushing cs call L1 ; push ip, jump to IRET endm ; POPFF macro TAB macro TYP push bx ; save for a moment and bx,mask FLG_&TYP ; isolate flags mov cl,FLG_&TYP ; shift amount shr bx,cl ; shift to low-order shl bx,1 ; times two to index table of words mov dx,TYP&MSG_TAB[bx] ; ds:dx => descriptive message pop bx ; restore mov ah,09h ; function code to display string int 21h ; request dos service endm ; TAB macro page INT_VEC segment at 0 ; start INT_VEC segment dd ? ; pointer to INT 00h INT01_OFF dw ? ; pointer to INT 01h INT01_SEG dw ? INT_VEC ends ; end INT_VEC segment PGROUP group STACK,CODE,DATA,MDATA ; The following segment both positions class 'prog' segments lower in ; memory than others so the first byte of the resulting .COM file is ; in the CODE segment, as well as satisfies the LINKer's need to have ; a stack segment. STACK segment byte stack 'prog' ; start STACK segment STACK ends ; end STACK segment I11_REC record I11_PRN:2,I11_RSV1:2,I11_COM:3,I11_RSV2:1,I11_DISK:2,I11_VID:2,I11_RSV3:2,I11_NDP:1,I11_IPL:1 DATA segment byte public 'data' ; start DATA segment assume ds:PGROUP OLDINT01_VEC label dword ; save area for original INT 01h handler OLDINT01_OFF dw ? OLDINT01_SEG dw ? NDP_CW label word ; save area for NDP control word db ? NDP_CW_HI db 0 ; high byte of control word NDP_ENV dw 7 dup(?) ; save area for NDP environment DATA ends subttl Message Data Area page MDATA segment byte public 'data' ; start MDATA segment assume ds:PGROUP MSG_START db 'CPUID -- Version 1.0' db CR,LF,CR,LF,EOS MSG_8088 db 'CPU is an Intel 8088.' db CR,LF,EOS MSG_8086 db 'CPU is an Intel 8086.' db CR,LF,EOS MSG_V20 db 'CPU is an NEC V20.' db CR,LF,EOS MSG_V30 db 'CPU is an NEC V30.' db CR,LF,EOS MSG_80188 db 'CPU is an Intel 80188.' db CR,LF,EOS MSG_80186 db 'CPU is an Intel 80186.' db CR,LF,EOS MSG_UNK db 'CPU is a maverick -- 80288??.' db CR,LF,EOS MSG_80286 db 'CPU is an Intel 80286.' db CR,LF,EOS CPUMSG_TAB label word dw PGROUP:MSG_8088 ; 000 = Intel 8088 dw PGROUP:MSG_8086 ; 001 = Intel 8086 dw PGROUP:MSG_V20 ; 010 = NEC V20 dw PGROUP:MSG_V30 ; 011 = NEC V30 dw PGROUP:MSG_80188 ; 100 = Intel 80188 dw PGROUP:MSG_80186 ; 101 = Intel 80186 dw PGROUP:MSG_UNK ; 110 = ? dw PGROUP:MSG_80286 ; 111 = Intel 80286 NDPMSG_TAB label word dw PGROUP:MSG_NDPX ; 00 = No NDP dw PGROUP:MSG_8087 ; 01 = Intel 8087 dw PGROUP:MSG_80287 ; 10 = Intel 80287 MSG_NDPX db 'NDP is not present.' db CR,LF,EOS MSG_8087 db 'NDP is an Intel 8087.' db CR,LF,EOS MSG_80287 db 'NDP is an Intel 80287.' db CR,LF,EOS CERRMSG_TAB label word dw PGROUP:MSG_CPUOK ; 0 = CPU healthy dw PGROUP:MSG_CPUBAD ; 1 = CPU faulty MSG_CPUOK db 'CPU appears to be healthy.' db CR,LF,EOS MSG_CPUBAD label byte db BEL,'*** CPU incorrectly allows interrupts ' db 'after a change to SS ***',CR,LF db 'It should be replaced with a more recent ' db 'version as it could crash the',CR,LF db 'system at seemingly random times.',CR,LF,EOS NERRMSG_TAB label word dw PGROUP:MSG_NDPSWOK ; 0 = NDP switch set correctly dw PGROUP:MSG_NDPSWERR ; 1 = NDP switch set incorrectly MSG_NDPSWOK db EOS ; no message MSG_NDPSWERR label byte db '*** Although there is an NDP installed ' db 'on this sytem, the corresponding',CR,LF db 'system board switch is not properly set. ' db 'To correct this, flip switch 2 of',CR,LF db 'switch block 1 on the system board.',CR,LF,EOS MDATA ends ; end MDATA segment subttl Main Routine page CODE segment byte public 'prog' ; start CODE segment assume cs:PGROUP,ds:PGROUP,es:PGROUP org 100h ; skip over PSP INITIAL proc near mov dx,offset ds:MSG_START ; starting message mov ah,09h ; function code to display string int 21h ; request DOS service call CPUID ; check the CPU's identity TAB CPU ; display CPU results TAB NDP ; display NDP results TAB CERR ; display CPU ERR results TAB NERR ; display NDP ERR results ret ; return to DOS INITIAL endp ; end INITIAL procedure subttl CPUID Procedure page CPUID proc near ; start CPUID procedure assume cs:PGROUP,ds:PGROUP,es:PGROUP ; This procedure determines the type of CPU and NDP (if any) in use. ; ; The possibilities include: ; ; Intel 8086 ; Intel 8088 ; NEC V20 ; NEC V30 ; Intel 80186 ; Intel 80188 ; Intel 80286 ; Intel 8087 ; Intel 80287 ; ; Also checked is whether or not the CPU allows interrupts after ; changing the SS register segment. If the CPU does, it is faulty ; and should be replaced. ; ; Further, if an NDP is installed, non-AT machines should have a ; system board switch set. Such a discrepancy is reported. ; ; On exit, BX contains flag settings (as defined in FLG record) which ; the caller can check. For example, to test for an Intel 80286, use ; ; and bx,mask FLAG_CPU ; cmp bx,FLG_80286 ; je ITSA286 irp XX,<ax,cx,di,ds,es> ; save registers push XX endm ; test for 80286 -- this CPU executes PUSH SP by first storing SP on ; stack, then decrementing it. earlier CPU's decrement, THEN store. mov bx,FLG_28 ; assume it's a 286 push sp ; only 286 pushes pre-push SP pop ax ; get it back cmp ax,sp ; check for same je CHECK_PIQL ; they are, so it's a 286 ; test for 80186/80188 -- 18xx and 286 CPU's mask shift/rotate ; operations mod 32; earlier CPUs use all 8 bits of CL. mov bx,FLG_18 ; assume it's an 8018x mov cl,32+1 ; 18x masks shift counts mod 32 ; note we can't use just 32 in CL mov al,0ffh ; start with all bits set shl al,cl ; shift one position if 18x jnz CHECK_PIQL ; some bits still on, ; so its a 18x, check PIQL ; test for V20 mov bx,FLG_NEC ; assume it's an NEC V-series CPU call CHECK_NEC ; see if it's an NEC chip jcxz CHECK_PIQL ; good guess, check PIQL mov bx,FLG_08 ; it's an 808x subttl Check Length of Pre-Fetch Instruction Queue page ; Check the length of the pre-fetch instruction queue (PIQ). ; ; xxxx6 CPUs have a PIQ length of 6 bytes, ; xxxx8 CPUs have a PIQ length of 4 bytes ; ; Self-modifying code is used to distinguish the two PIQ lengths. CHECK_PIQL: call PIQL_SUB ; handle via subroutine jcxz CHECK_ERR ; if CX is 0, INC was not executed, ; hence PIQ length is 4 or bx,FLG_PIQL ; PIQ length is 6 subttl Check for Allowing Interrupts After POP SS page ; Test for faulty chip (allows interrupts after change to SS register) CHECK_ERR: xor ax,ax ; prepare to address ; interrupt vector segment mov ds,ax ; DS points to segment 0 assume ds:INT_VEC ; tell the assembler cli ; nobody move while we swap mov ax,offset cs:INT01 ; point to our own handler xchg ax,INT01_OFF ; get and swap offset mov OLDINT01_OFF,ax ; save to restore later mov ax,cs ; our handler's segment xchg ax,INT01_SEG ; get and swap segment mov OLDINT01_SEG,ax ; save to restore later ; note we continue with interrupts disabled to avoid ; an external interrupt occuring during this test mov cx,1 ; initialize a register push ss ; save ss to store back into itself pushf ; move flags pop ax ; ... into ax or ax,mask TF ; set trap flag push ax ; place onto stack POPFF ; ... and then into effect ; some CPUs effect the trap flag ; immediately, some ; wait one instruction nop ; allow interrupt to take effect POST_NOP: pop ss ; change the stack segment register ; (to itself) dec cx ; normal cpu's execute this instruction ; before recognizing the single-step ; interrupt hlt ; we never get here INT01: ; Note: IF=TF=0 ; If we're stopped at or before POST_NOP, continue on push bp ; prepare to address the stack mov bp,sp ; hello, Mr. stack cmp [bp].ARG_OFF,offset cs:POST_NOP ; check offset pop bp ; restore ja INTO1_DONE ; we're done iret ; return to caller INTO1_DONE: ; restore old INT 01h handler les ax,OLDINT01_VEC ; ES:AX ==> old INT 01h handler assume es:nothing ; tell the assembler mov INT01_OFF,ax ; restore offset mov INT01_SEG,es ; ... and segment sti ; allow interrupts again (IF=1) add sp,3*2 ; strip ip, cs, and flags from stack push cs ; setup ds for code below pop ds assume ds:PGROUP ; tell the assembler jcxz CHECK_NDP ; if cx is 0, the dec cx was executed, ; and the cpu is ok or bx,mask FLG_CERR ; it's a faulty chip subttl Check For Numeric Data Processor page ; Test for a Numeric Data Processor -- Intel 8087 or 80287. The ; technique used is passive -- it leaves the NDP in the same state in ; which it is found. CHECK_NDP: cli ; protect FNSTENV fnstenv NDP_ENV ; if NDP present, save ; current environment, ; otherwise, this instruction ; is ignored mov cx,50/7 ; cycle this many times loop $ ; wait for result to be stored sti ; allow interrupts fninit ; initialize processor to known state jmp short $+2 ; wait for initialization fnstcw NDP_CW ; save control word jmp short $+2 ; wait for result to be stored jmp short $+2 cmp NDP_CW_HI,03h ; check for NDP initial control word jne CPUID_EXIT ; no NDP installed int 11h ; get equipment flags into ax test ax,mask I11_NDP ; check NDP-installed bit jnz CHECK_NDP1 ; it's correctly set or bx,mask FLG_NERR ; mark as in error CHECK_NDP1: and NDP_CW,not mask IEM ; enable interrupts ; (IEM=0, 8087 only) fldcw NDP_CW ; reload control word fdisi ; disable interrupts (IEM=1) on 8087, ; ignored by 80287 fstcw NDP_CW ; save control word fldenv NDP_ENV ; restore original NDP environment ; no need to wait ; for environment to be loaded test NDP_CW,mask IEM ; check interrupt enable mask ; (8087 only) jnz CPUID_8087 ; it changed, hence NDP is an 8087 or bx,FLG_287 ; NDP is an 80287 jmp short CPUID_EXIT ; exit with falgs in BX CPUID_8087: or bx,FLG_87 ; NDP is an 8087 CPUID_EXIT: irp XX,<es,ds,di,cx,ax> ; restore registers pop XX endm assume ds:nothing,es:nothing ret ; return to caller CPUID endp ; end CPUID procedure subttl Check For NEC V20/V30 page CHECK_NEC proc near ; The NEC V20/V30 are very compatible with the Intel 8086/8088. ; The only point of "incompatibility" is that they do not contain ; a bug found in the Intel CPU's. Specifically, the NEC CPU's ; correctly restart an interrupted multi-prefix string instruction ; at the start of the instruction. The Intel CPU's incorrectly ; restart in the middle of the instruction. This routine tests ; for that situation by executing such an instruction for a ; sufficiently long period of time for a timer interrupt to occur. ; If at the end of the instruction, CX is zero, it must be an NEC ; CPU; if not, it's an Intel CPU. ; ; Note that we're counting on the timer interrupt to do its thing ; every 18.2 times per second. ; ; Here's a worst case analysis: An Intel 8088/8086 executes 65535 ; iterations of LODSB ES[SI] in 2+9+13*65535 = 851,966 clock ticks. ; If the Intel 8088/8086 is running at 10 MHz, each clock tick is ; 100 nanoseconds, hence the entire operation takes 85 milliseconds. ; If the timer is running at normal speed, it interrupts the CPU every ; 55ms and so should interrupt the repeated string instruction at least ; once. mov cx,0ffffh ; move a lot of data sti ; ensure timer enabled ; execute multi-prefix instruction. note that the value of ES as ; well as the direction flag setting is irrelevant. push ax ; save registers push si rep lods byte ptr es:[si] pop si ; restore pop ax ; on exit: if cx is zero, it's an NEC CPU, otherwise it's an Intel CPU ret ; return to caller CHECK_NEC endp subttl Pre-Fetch Instruction Queue Subroutine page PIQL_SUB proc near ; This subroutine discerns the length of the CPU's pre-fetch ; instruction queue (PIQ). ; ; The technique used is to first ensure that the PIQ is full, then ; change an instruction which should be in a 6-byte PIQ but not in a ; 4-byte PIQ. Then, if the original instruction is executed, the PIQ ; is 6-bytes long; if the new instruction is executed, PIQ length is 4. ; ; We ensure the PIQ is full be executing an instruction which takes ; long enough so that the Bus Interface Unit (BIU) can fill the PIQ ; while the instruction is executing. ; ; Specifically, for all byt the last STOSB, we're simple marking time ; waiting for the BIU to fill the PIQ. The last STOSB actually changes ; the instruction. By that time, the orignial instruction should be in ; a six-byte PIQ byt not a four-byte PIQ. assume cs:PGROUP,es:PGROUP @REP equ 3 ; repeat the store this many times std ; store backwards mov di,offset es:LAB_INC+@REP-1 ; change the instructions ; at ES:DI ; and preceding mov al,ds:LAB_STI ; change to a sti mov cx,@REP ; give the BIU time ; to pre-fetch instructions cli ; ensure interrupts are disabled, ; otherwise a timer tick ; could change the PIQ filling rep stosb ; change the instruction ; during execution of this instruction ; the BIU is refilling the PIQ. The ; current instruction is no longer ; in the PIQ. ; Note at end, CX is 0. ; The PIQ begins filling here cld ; restore direction flag nop ; PIQ fillers nop nop ; The following instruction is beyond a four-byte-PIQ CPU's reach, ; but within that of a six-byte-PIQ CPU. LAB_INC label byte inc cx ; executed only if PIQ length is 6 LAB_STI label byte rept @REP-1 sti ; restore interrupts endm ret ; return to caller assume ds:nothing,es:nothing PIQL_SUB endp ; end PIQL_SUB procedure CODE ends ; end code segment if1 %OUT Pass 1 Complete else %OUT Pass 2 Complete endif end INITIAL ; end CPUID module begin 644 cpuid.com MNFD"M G-(>A1 %.!XP< L0#3Z]'CBY=' UNT"<TA4X'C& "Q ]/KT>.+EU<# M6[0)S2%3@>,@ +$%T^O1XXN7I -;M G-(5.!XT L0;3Z]'CBY=R!%NT"<TA MPU!15QX&NP8 5%@[Q'06NP0 L2&P_]+@=0N[ @#HN@#C [L .B^ .,$@<L! M #/ CMCZN+<!AP8$ "ZC50*,R(<&!@ NHU<"N0$ %IQ8#0 !4.L!SP[H^_^0 M%TGT58OL@7X"M %==P'/+L0&50*C! ",!@8 ^X/$!@X?XP2!RR ^MDV6P*Y M!P#B_OO;X^L V3Y9 NL ZP" /EH" W4US1&I @!U!('+0 "!)ED"?_^;V2Y9 M IO;X9O9/ED"F]DF6P+W!ED"@ !U!H'+$ #K!('+" ''U]96,.Y___[4%;S M)JQ>6,/]OU,"H%("N0, ^O.J_)"0D$'[^\, M $-054E$("TM(%9E<G-I;VX@,2XP#0H-"B1#4%4@:7,@86X@26YT96P@.# X M."X-"B1#4%4@:7,@86X@26YT96P@.# X-BX-"B1#4%4@:7,@86X@3D5#(%8R M,"X-"B1#4%4@:7,@86X@3D5#(%8S,"X-"B1#4%4@:7,@86X@26YT96P@.# Q M.#@N#0HD0U!5(&ES(&%N($EN=&5L(#@P,3@V+@T*)$-052!I<R!A(&UA=F5R M:6-K("TM(#@P,C@X/S\N#0HD0U!5(&ES(&%N($EN=&5L(#@P,C@V+@T*)((" MF@*R L<"W +U @X#+@-= W,#BP-.1% @:7,@;F]T('!R97-E;G0N#0HD3D10 M(&ES(&%N($EN=&5L(#@P.#<N#0HD3D10(&ES(&%N($EN=&5L(#@P,C@W+@T* M)*@#Q0-#4%4@87!P96%R<R!T;R!B92!H96%L=&AY+@T*) <J*BH@0U!5(&EN M8V]R<F5C=&QY(&%L;&]W<R!I;G1E<G)U<'1S(&%F=&5R(&$@8VAA;F=E('1O M(%-3("HJ*@T*270@<VAO=6QD(&)E(')E<&QA8V5D('=I=&@@82!M;W)E(')E M8V5N="!V97)S:6]N(&%S(&ET(&-O=6QD(&-R87-H('1H90T*<WES=&5M(&%T M('-E96UI;F=L>2!R86YD;VT@=&EM97,N#0HD=@1W!"0J*BH@06QT:&]U9V@@ M=&AE<F4@:7,@86X@3D10(&EN<W1A;&QE9"!O;B!T:&ES('-Y=&5M+"!T:&4@ M8V]R<F5S<&]N9&EN9PT*<WES=&5M(&)O87)D('-W:71C:"!I<R!N;W0@<')O M<&5R;'D@<V5T+B @5&\@8V]R<F5C="!T:&ES+"!F;&EP('-W:71C:" R(&]F M#0IS=VET8V@@8FQO8VL@,2!O;B!T:&4@<WES=&5M(&)O87)D+@T*)" @(" M+BXN+BXN+BXN+BXN+BXN+BXN+BXN+BXN "X@+BXN("XN+BTN+BXN #$R,# ;($XX,4X@(" @(" @(" +BXN+BXN+BXN+BXN end -- Peter Fales UUCP: ...ihnp4!ihlpe!psfales work: (312) 979-7784 AT&T Information Systems, IW 1Z-243 1100 E. Warrenville Rd., IL 60566