            PAGE    ,120

;-------------------------------------------------------------------------------
;
; This test program CPUTEST2 demonstrates the capabilities of subroutine
; CPU_NDP_CHK. CPUTEST2 will only run on true PC compatibles. To produce
; an executable COM file, proceed as follows:
;
; MASM CPUTEST2;
; LINK COUTEST2;
; EXE2BIN CPUTEST2.EXE CPUTEST2.COM
;
; use MASM 5.1 or TASM 2.01 to assemble this file
;
; CPU_NDP_CHK is (c) 1988-1991 Norbert Juffa, this version dated 09-14-1991
;
; Code to check for IIT NDPs courtesy of IIT Corporation Europe 02-19-1991
;
;-------------------------------------------------------------------------------

            .386p                        ; 386 and 387 code
            .387                         ;  will be used

JMPS        EQU     <JMP SHORT>          ; jumps have to be
JES         EQU     <JE  SHORT>          ;  declared as short,
JBS         EQU     <JB  SHORT>          ;   since the default
JNZS        EQU     <JNZ SHORT>          ;    distance with the .386
JCS         EQU     <JC  SHORT>          ;     directive is near
JZS         EQU     <JZ  SHORT>          ;      jumps, which can
JNES        EQU     <JNE SHORT>          ;       only be executed on
JAES        EQU     <JAE SHORT>          ;        386 and 486
JAS         EQU     <JA  SHORT>          ;         machines


STRT_TIM    MACRO
            MOV     AL, 0B4h             ; programm clock 2
            OUT     43h, AL              ;  as rate generator
            XOR     AL, AL               ; initialize
            OUT     42h, AL              ;  timer 2
            OUT     42h, AL              ;   to zero
            ENDM

STOP_TIM    MACRO
            MOV     AL, 80h              ; timer 2
            OUT     43h, AL              ; latch immediately
            IN      AL, 42h              ; read LSB
            MOV     BL, AL               ; save LSB
            IN      AL, 42h              ; read MSB
            MOV     BH, AL               ; save MSB
            NEG     BX                   ; negate for timer counts
            ENDM


_TEXT       SEGMENT BYTE PUBLIC USE16 'CODE'
            ASSUME  CS:_TEXT, DS:_TEXT   ; CS=DS=ES=SS for COM program

            ORG     100h                 ; to make COM program

MainStart:  MOV     DX, OFFSET IntroMsg  ; pointer to introductory message
            MOV     AH, 9                ; DOS #9, print string
            INT     21h                  ; call DOS
            CALL    CPU_NDP_CHK          ; determine installed CPU and NDP
            MOV     BX, AX               ; save processor types
            MOV     DX, OFFSET CPUMsg    ; pointer to CPU message
            MOV     AH, 9                ; DOS #9, print string
            INT     21h                  ; call DOS
            MOV     AX, BX               ; get processor types (AL = CPU)
            MOV     AH, 14               ; compute pointer
            MUL     AH                   ;  to message for
            ADD     AX, OFFSET Processor ;   this CPU type
            XCHG    AX, DX               ; pointer to correct CPU type message
            MOV     AH, 9                ; DOS #9, print string
            INT     21h                  ; call DOS
            MOV     DX, OFFSET EOL       ; get pointer to CR-LF
            MOV     AH, 9                ; DOS #9, print string
            INT     21h                  ; call DOS
            MOV     DX, OFFSET NDPMsg    ; get pointer to NDP message
            MOV     AH, 9                ; DOS #9, print string
            INT     21h                  ; call DOS
            XCHG    AX, BX               ; get processor types (AH = NDP)
            MOV     AL, 20               ; compute pointer
            MUL     AH                   ;  to message for
            ADD     AX,OFFSET Coprocessor;   this NDP type
            XCHG    AX, DX               ; pointer to correct CPU type message
            MOV     AH, 9                ; DOS #9, print string
            INT     21h                  ; call DOS
            MOV     DX, OFFSET EOL       ; get pointer to CR-LF
            MOV     AH, 9                ; DOS #9, print string
            INT     21h                  ; call DOS
            MOV     AX, 4C00h            ; DOS #4C, terminate program, errlvl 0
MainEnd:    INT     21h                  ; call DOS


;-------------------------------------------------------------------------------
; CPU_NDP_CHK is a subroutine which returns the currently installed CPU and
; numerical coprocessor. This routine requires an IBM compatible PC to run
; correctly. It uses ports 40h-42h to keep time with the timer 2 of the 8253
; chip of those computers and uses port 61h (port B - system control ) to
; control input to the timer. This routine must be executed in the real mode
; of the i286, i386(sx), and i486 CPUs.
;
; INPUT:       NONE                       CPU           NDP
; OUTPUT:      AH   - NDP type            -----------    ---------------
;              AL   - CPU type            n.a.   -  0    none       -  0
;                                         8088   -  1    Emulator   -  1
; DESTROYS:    AX,BX,CX,DX,SI,DI,Flags    8086   -  2    8087       -  2
;              NDP status not preserved   V20    -  3    80187      -  3
;                                         V30    -  4    80287      -  4
;                                         80188  -  5    80287XL    -  5
;                                         80186  -  6    80387      -  6
;                                         80286  -  7    80387sx    -  7
;                                         80386  -  8    IIT2C87    -  8
;                                         80386sx-  9    IIT2C87    -  9
;                                         80486  - 10    IIT3C87    - 10
;                                         80486sx- 11    IIT3C87sx  - 11
;                                                        Cyrix82D87 - 12
;                                                        Cyrix82D87 - 13
;                                                        Cyrix83D87 - 14
;                                                        Cyrix83S87 - 15
;                                                        80387DX    - 16
;                                                        80486/487sx- 17
;-------------------------------------------------------------------------------

            PUBLIC  CPU_NDP_CHK
            ASSUME  CS: _TEXT, DS: _TEXT

Stat        EQU     [BP-2]               ; 80x87 status word
Ctrl        EQU     [BP-4]               ; 80x87 control word
SystemStat  EQU     [BP-6]               ; value of PC port B (system status)
AAMTime     EQU     [BP-8]               ; time needed to execute 200 AAMs
MoveWrdTime EQU     [BP-10]              ; time needed to move 5000 words
MoveDblTime EQU     [BP-12]              ; time needed to move 5000 double words
SqrtTime    EQU     [BP-14]              ; time to execute 40 FSQRTs
CPU_NDP     EQU     [BP-16]              ; memory location to hold CPU and NDP
BCDValue    EQU     [BP-26]              ; holds a BCD operand

CPU_NDP_CHK PROC    NEAR
            PUSH    BP                   ; save caller's framepointer
            MOV     BP, SP               ; make new framepointer
            SUB     SP, 26               ; get memory for local variables
            PUSH    DS                   ; save caller's data segment
            PUSH    CS                   ; set data segment
            POP     DS                   ;  to code segment

$inittimer: CLI                          ; no interrupts, please
            CLD                          ; auto increment for string operations
            IN      AL, 61h              ; port B - system control
            MOV     [SystemStat], AL     ; save system status
            AND     AL, 11111101b        ; switch off speaker bit
            OR      AL, 1                ; switch on bit for clock 2
            CMP     AL, [SystemStat]     ; system already configured correct ?
            JES     $cpu_ndptst          ; yes, don't write to system ctrl port
            OUT     61h, AL              ; write back to system control

$cpu_ndptst:PUSH    SP                   ; test updating
            POP     AX                   ;  of stackpointer
            CMP     AX, SP               ; stackpointer updated before push ?
            JES     $286_386             ; no, must be 286, 386 or 486
            MOV     AX, 1                ; try to shift
            MOV     CL, 33               ;  accu 33 times
            SHL     AX, CL               ; shift count masked off ?
            JNZS    $186_188             ; yes, must be 186 or 188
            PUSHA                        ; PUSHA executed on 88/86 as JMP $+2
            STC                          ; carry set if V20 or V30
            JCS     $V20_V30             ; yes, must be V20 or V30
            PUSHF                        ; save flags
            POP     AX                   ; pop flags into AX
            AND     AX, 0FFFH            ; clear bits 12-15 of flag register
            PUSH    AX                   ; put new flags in stack
            POPF                         ; pop into flag register
            PUSHF                        ; put flags on stack
            POP     AX                   ; get flags
            AND     AH, 0F0H             ; test if all bits
            CMP     AH, 0F0H             ;  in highest nibble set
            JES     $88_86               ; all bits in highest nibble set
            XOR     DL, DL               ; failed all tests, unknown CPU
            JMPS    $ndp_test            ; go and test ndp
$88_86:     MOV     DL, 1                ; else its an 88 or 86
            JMPS    $queue_test          ; decide wether 88 or 86
$V20_V30:   POPA                         ; remove pushed bytes
            MOV     DL, 3                ; its an V20 or V30
            JMPS    $queue_test          ; decide wether V20 or V30
$186_188:   MOV     DL, 5                ; 188/186
            JMPS    $queue_test          ; decide wether 188 or 186
$286_386:   MOV     DL, 7                ; 286, 386 or 486
            PUSH    7000h                ;  try to set
            POPF                         ;   IOPL and NT fields
            PUSHF                        ;    in bit 12-14
            POP     AX                   ;     of flag register
            TEST    AX, 7000h            ; bits cannot be set in 286 real mode
            JZS     $ndp_test            ; bits not set --> 286
            INC     DX                   ; CPU is an 386 (DL = 8) or 486
            MOV     EBX, ESP             ; save current stackpointer to align it
            AND     ESP, 0FFFFFFFCh      ; align stack to avoid AC fault
            PUSHFD                       ; save EFLAGS
            POP     EAX                  ; get EFLAGS from stack
            MOV     ECX, EAX             ; original value of EFLAGS
            XOR     EAX, 40000H          ; toggle AC bit in EFLAGS
            PUSH    EAX                  ; copy new value
            POPFD                        ;  to EFLAGS
            PUSHFD                       ; get new EFLAGS value
            POP     EAX                  ; put into EAX
            XOR     EAX, ECX             ; test if AC bit could be changed
            SHR     EAX, 18              ; EAX = 0 if 386, 1 if 486
            PUSH    ECX                  ; restore original
            POPFD                        ;  value of EFLAGS
            MOV     ESP, EBX             ; restore original stack pointer
            AND     EAX, 1               ; check CPU code in EAX
            JZS     $ndp_test            ; if 0, must be 386
            MOV     DL, 10               ; its a 486 (CPU = 10)
            JMP     $ndp_test            ; check for FPU (486sx <-> 486)
$queue_test:MOV     BX, OFFSET $patch    ; load patch adress into BX
            MOV     BYTE PTR [BX], 42h   ; preset with opcode for INC DX
            MOV     AL, 90H              ; patch in a NOP (opcode 90h)
            MOV     CL, 31               ; rotate register 31 times to use up
            ROL     AH, CL               ;  time so prefetch queue can be filled
            MOV     BYTE PTR [BX], AL    ; insert NOP at label $patch
            NOP                          ; fill
            NOP                          ;  prefetch
            NOP                          ;   queue
            NOP                          ;    with NOPs
$patch:     INC     DX                   ; patched to NOP on i88, i188 and V20
$ndp_test:  XOR     DH, DH               ; assume no coprocessor
            XOR     AX, AX               ; clear register
            OUT     0F0h, AL             ; clear error signal
            FNINIT                       ; initialize coprocessor
            MOV     [Ctrl], AX           ; clear status variable
            NOT     AX                   ; load all 1's
            MOV     [Stat], AX           ; initialize status variable to all 1's
            FNSTCW  [Ctrl]               ; store NDP control word
            MOV     AX, [Ctrl]           ; get control word
            AND     AX, 0F3Fh            ; extract RC, PC and exception masks
            CMP     AX, 033Fh            ; RC=0, PC=3, masks=3F ?
            JES     $chk_stat            ; OK, now check status word
            CMP     DL, 10               ; no NDP. CPU = 486 ?
            SBB     DL, -1               ; yes, 486 w/o FPU = 486sx
            JMPS    $ndp_exit            ; no further FPU tests required
$chk_stat:  FNSTSW  [Stat]               ; store NDP status
            TEST    WORD PTR [Stat],383Fh; stack top & exceptions must be clear
            JNZS    $ndp_exit            ; ST & exceptions not clear -> no NDP
            MOV     DH, 1                ; coprocessor is at least emulator (=1)
            CMP     DL, 7                ; is CPU 80286 or higher ?
            JBS     $no_emulat           ; no, emulation impossible
            SMSW    AX                   ; get machine status word
            TEST    AL, 4                ; test if EM bit of MSW set
            JNZS    $ndp_exit            ; yes, NDP only emulated
$no_emulat: MOV     DH, 2                ; coprocessor is at least 8087 (=2)
            FLD1                         ; load 1.0
            WAIT                         ; needed for 8087
            FLDZ                         ; load 0.0
            WAIT                         ; needed for 8087
            FDIV                         ; 1.0 / 0.0 = +infinity
            WAIT                         ; needed for 8087
            FLD     ST(0)                ; duplicate +infinity
            WAIT                         ; needed for 8087
            FCHS                         ; generate -infinity
            WAIT                         ; needed for 8087
            FCOMPP                       ; compare infinities and clear NDP stk
            WAIT                         ; needed for 8087
            FSTSW   WORD PTR [Stat]      ; save condition codes
            MOV     AX, [Stat]           ; load condition codes
            SAHF                         ; transfer into CPU flags
            JNES    $187_387             ; 187, C287, 387, 486 if numbers n. eq.
            CMP     DL, 7                ; is CPU >= 286 ?
            JBS     $ndp_exit            ; no, coprocessor is 8087
            MOV     DH, 4                ; coprocessor is 287
            JMPS    $chk_iit             ; check for IIT coprocessors
$187_387:   CMP     DL, 7                ; is CPU >= 286 ?
            JAES    $C287_387            ; yes, NDP is one of C287,287XL,387,486
            MOV     DH, 3                ; coprocessor is 187
            JMPS    $ndp_exit            ; store CPU and NDP code
$C287_387:  CMP     DL, 8                ; is CPU >= 386 ?
            JAES    $387_486             ; yes, NDP is 387, 387sx, 486, or 487
            MOV     DH, 5                ; coprocessor is C287
            JMPS    $chk_iit             ; check for IIT coprocessors
$387_486:   CMP     DL, 10               ; is CPU >= 486 ?
            JAES    $486                 ; yes, NDP is 486 / 487
            MOV     DH, 6                ; coprocessor is 387 or 387sx
            JMPS    $chk_iit             ; check for IIT coprocessors
$486:       MOV     DH, 17               ; NDP = 486 / 487
            JMPS    $ndp_exit            ; no further tests required
$chk_iit:   FINIT                        ; initialize coprocessor
            FWAIT                        ; wait until operation complete
            FLD     [$tst_val]           ; load denormal number
            FWAIT                        ; wait until operation complete
            FADD    ST(0), ST            ; result is a normal
            FWAIT                        ; wait until operation complete
            FSTSW   AX                   ; get status of NDP into AX
            AND     AX, 2                ; test if denormal exception flag set
            JNZS    $ndp_exit            ; Intel NDPs signal denormal exception
            ADD     DH, 4                ; set IIT coprocessor types
$ndp_exit:  MOV     [CPU_NDP], DX        ; save CPU and NDP types

$aam:       STRT_TIM                     ; start timer 2
            REPT    200
            AAM                          ; execute AAM 200 times
            ENDM
            STOP_TIM                     ; elapsed time of timer 2 in BX
            MOV     [AAMTime], BX        ; save time used for AAM

$moveword:  CMP     BYTE PTR [CPU_NDP], 8; CPU = iAPX 386 ?
            JBS     $test_87             ; no, not a 386 type
            MOV     AX, DS               ; set up segment
            MOV     ES, AX               ;  registers for string move
            XOR     SI, SI               ; offset into segment
            MOV     DI, SI               ;  is zero for both segment
            STRT_TIM                     ; start timer 2
            MOV     CX, 5000             ; move 5000
            REP     MOVSW                ;  words
            STOP_TIM                     ; elapsed time of timer 2 in BX
            MOV     [MoveWrdTime], BX    ; save MoveWordTime

$movedouble:MOV     AX, DS               ; set up segment registers
            MOV     ES, AX               ;  for string move
            XOR     SI, SI               ; offset into both segment
            MOV     DI, SI               ;  is zero
            STRT_TIM                     ; start timer 2
            MOV     CX, 5000             ; move 5000
            REP     MOVSD                ;  double words
            STOP_TIM                     ; elapsed time of timer 2 in BX
            MOV     [MoveDblTime], BX    ; save MoveDoubleTime

$chk_386sx: MOV     AX, [MoveWrdTime]    ; AX = MoveWord-Time
            XCHG    AX, BX               ; AX=MoveDouble-Time, BX=MoveWord-Time
            SUB     AX, BX               ; MoveDTime - MoveWTime
            ADD     AX, AX               ; 2 * (MoveDTime - MoveWTime)
            CWD                          ; compute
            XOR     AX, DX               ;  Abs (2 * (MoveDoubleTime -
            SUB     AX, DX               ;   MoveWordTime))
            CMP     BX, AX               ; Abs(2*(MovDTime-MovWTime))>MovWTime?
            ADC     WORD PTR [CPU_NDP], 0; CPU Typ = 386sx if true

$test_87:   CMP     BYTE PTR [CPU_NDP+1],1; has system coprocessor ?
            JAS     $cont_87             ; yes, do tests on coprocessor
            JMP     $no_fpu              ; no, skip tests

$cont_87:   FNINIT                       ; initialize coprocessor
            NOP                          ; use instead of WAIT
            FLD1                         ; load 1.0
            NOP                          ; use instead of WAIT
            STRT_TIM                     ; start timer 2
            REPT    40
            FSQRT                        ; do 40 FSQRTs followed
            NOP                          ;  by NOP
            ENDM
            STOP_TIM                     ; elapsed time of timer 2 in BX
            MOV   [SqrtTime], BX         ; save time needed for SQRT

            MOV   CX, [CPU_NDP]          ; get CPU (CL) and NDP (CH)
            CMP   CL, 9                  ; CPU > 80386sx ?
            JAS   $store_type            ; yes, no further testing
            CMP   CH, 7                  ; NDP > 387 ?
            JAS   $no_cyrix              ; yes, IIT type
            CMP   CH, 4                  ; NDP < 287 ?
            JBS   $store_type            ; yes, no need to check for Cyrix type
$chk_cyrix: MOV   DX, [AAMTime]          ; AAM time
            MOV   AX, BX                 ; 40 * SQRT-287 time
            SHR   AX, 1                  ; 20 * SQRT-287 time
            ADD   AX, BX                 ; 60 * SQRT-287 time
            CMP   AX, DX                 ; 60 * SQRT-287 time < 200 * AAM time ?
            JAS   $no_cyrix              ; no, not a Cyrix
            ADD   CH, 8                  ; set Cyrix types
$no_cyrix:  CMP   CL, 9                  ; CPU = 80386sx ?
            JNES  $no_387sx              ; no
            INC   CH                     ; set SX types of coprocessors
            JMPS  $store_type            ; SX-types certainly no DX-types
$no_387sx:  CMP   CH, 6                  ; NDP = Intel 387 ?
            JNES  $store_type            ; no, check for 387DX makes no sense
$chk_387dx: FINIT                        ; initialize coprocessor
            FLD1                         ; load
            FLD1                         ;  three
            FLD1                         ;   1.0's
            STRT_TIM                     ; start timer 2
            FBSTP TBYTE PTR [BCDValue]   ; write
            FBSTP TBYTE PTR [BCDValue]   ;  three times in
            FBSTP TBYTE PTR [BCDValue]   ;   BCD format
            MOV   AL, 80h                ; latch
            OUT   43h, AL                ;  timer 2
            IN    AL, 42h                ; read LSB of timer value
            MOV   DL, AL                 ; save LSB
            IN    AL, 42h                ; read MSB of timer value
            MOV   DH, AL                 ; save MSB
            NEG   DX                     ; timer counts
            ADD   DX, DX                 ; time for six FBSTPs
            ADD   DX, DX                 ; time for twelve FBSTPs
            CMP   DX, BX                 ; 12 * FBSTP-time < 40 * SQRT-time ?
            JAS   $store_type            ; no, can not be 387DX
            MOV   CH, 16                 ; set NDP-type to 387DX
$store_type:MOV   [CPU_NDP], CX          ; save CPU and NDP type

$no_fpu:    MOV   AL, [SystemStat]       ; get original system status
            OUT   61h, AL                ; and restore it
            STI                          ; interrupts ok now

$ende:      POP   DS                     ; get callers framepointer
            MOV   AX, [CPU_NDP]          ; load return value
            MOV   SP, BP                 ; get rid of local variables
            POP   BP                     ; restore callers framepointer
            RET                          ; done

$tst_val    DT      1                    ; test value for IIT coprocessor check

CPU_NDP_CHK ENDP


IntroMsg    DB      79 DUP ('-'), 10, 13
            DB      20 DUP (' '), 'C P U - T e s t  2  (c) 1988-1991 N.J.',10,13
            DB      79 DUP ('-'), 10, 13, 10, 13, '$'

CPUMsg      DB      'CPU ist $'
NDPMsg      DB      'NDP ist $'
EOL         DB      10, 13, '$'

Processor   DB      'unbekannt    $'
            DB      'Intel 8088   $'
            DB      'Intel 8086   $'
            DB      'NEC V20      $'
            DB      'NEC V30      $'
            DB      'Intel 80188  $'
            DB      'Intel 80186  $'
            DB      'Intel 80286  $'
            DB      'Intel 80386  $'
            DB      'Intel 80386sx$'
            DB      'Intel 80486  $'
            DB      'Intel 80486sx$'

Coprocessor DB      'nicht vorhanden    $'
            DB      'Emulator via INT 7 $'
            DB      'Intel 8087         $'
            DB      'Intel 80187        $'
            DB      'Intel 80287        $'
            DB      'Intel 80287XL      $'
            DB      'Intel 80387        $'
            DB      'Intel 80387sx      $'
            DB      'IIT 2C87           $'
            DB      'IIT 2C87           $'
            DB      'IIT 3C87           $'
            DB      'IIT 3C87sx         $'
            DB      'Cyrix 82D87        $'
            DB      'Cyrix 82D87        $'
            DB      'Cyrix 83D87        $'
            DB      'Cyrix 83S87        $'
            DB      'Intel 80387DX      $'
            DB      'Teil des 80486     $'

_TEXT       ENDS

            END     MainStart


