6.2.4 Using Coprocessor Instructions

The 8087 family of coprocessors has separate instructions for each of the following operations:

Loading and storing data

Doing arithmetic calculations

Controlling program flow

The following sections explain the available instructions and show how to use them for each of the operations listed above. See Section 6.2.2, “Instruction and Operand Formats,” for general syntax information.

6.2.4.1 Loading and Storing Data

Data-transfer instructions transfer data between main memory and the coprocessor registers or between different coprocessor registers. Two principles govern data transfers:

The choice of instruction determines whether a value in memory is considered an integer, a BCD number, or a real number. The value is always considered a 10-byte real number once it is transferred to the coprocessor.

The size of the operand determines the size of a value in memory. Values in the coprocessor always take up 10 bytes.

Summary: Load commands transfer data, and store commands remove data.

You can transfer data to stack registers using load commands. These commands push data onto the stack from memory or from coprocessor registers. Store commands remove data. Some store commands pop data off the register stack into memory or coprocessor registers; others simply copy the data without changing it on the stack.

If you use constants as operands, you cannot load them directly into coprocessor registers. You must allocate memory and initialize a variable to a constant value. That variable can then be loaded by using one of the load instructions listed below.

A few special instructions are provided for loading certain constants. You can load 0, 1, pi, and several common logarithmic values directly. Using these instructions is faster and often more precise than loading the values from initialized variables.

All instructions that load constants have the stack top as the implied destination operand. The constant to be loaded is the implied source operand.

The coprocessor data area, or parts of it, can also be moved to memory and later loaded back. You may want to do this to save the current state of the coprocessor before executing a procedure. After the procedure ends, restore the previous status. Saving coprocessor data is also useful when you want to modify coprocessor behavior by writing certain data to main memory, operating on the data with 8086-family instructions, and then loading it back to the coprocessor data area.

You can use the following instructions for transferring numbers to and from registers:

Instruction(s) Description

FLD, FST, FSTP Loads and stores real numbers
FILD, FIST, FISTP Loads and stores binary integers
FBLD Loads BCD
FBSTP Stores BCD
FXCH Exchanges register values
FLDZ Pushes 0 into ST
FLD1 Pushes 1 into ST
FLDPI Pushes the value of pi into ST
FLDCW mem2byte Loads the control word into the coprocessor
F[[N]]STCW mem2byte, Stores the control word in memory  
FLDENV mem14byte Loads environment from memory
F[[N]]STENV mem14byte Stores environment in memory
FRSTOR mem94byte Restores state from memory
F[[N]]SAVE mem94byte, Saves state in memory  
FLDL2E Pushes the value of log2e into ST
FLDL2T Pushes log210 into ST
FLDLG2 Pushes log102 into ST
FLDLN2 Pushes loge2 into ST

The following example and Figure 6.7 illustrate some of these instructions:

.DATA

m1 REAL4 1.0

m2 REAL4 2.0

.CODE

fld m1 ; Push m1 into first item

fld st(2) ; Push third item into first

fst m2 ; Copy first item to m2

fxch st(2) ; Exchange first and third items

fstp m1 ; Pop first item into m1

6.2.4.2 Doing Arithmetic Calculations

Most of the coprocessor instructions for doing arithmetic operations have several forms, depending on the operand used. You do not need to specify the operand type in the instruction if both operands are stack registers, since register values are always 10-byte real numbers. The arithmetic instructions are listed below. In most cases, the result replaces the destination register.

Instruction Description

FADD Adds the source and destination
FSUB Subtracts the source from the destination
FSUBR Subtracts the destination from the source
FMUL Multiplies the source and the destination
FDIV Divides the destination by the source
FDIVR Divides the source by the destination
FABS Sets the sign of ST to positive
FCHS Reverses the sign of ST
FRNDINT Rounds ST to an integer
FSQRT Replaces the contents of ST with its square root
FSCALE Multiplies the stack-top value by 2 to the power contained in ST(1)
FPREM Calculates the remainder of ST divided by ST(1)

80387 Only

Instruction Description

FSIN Calculates the sine of the value in ST
FCOS Calculates the cosine of the value in ST
FSINCOS Calculates the sine and cosine of the value in ST
FPREM1 Calculates the partial remainder by performing modulo division on the top two stack registers
FXTRACT Breaks a number down into its exponent and mantissa and pushes the mantissa onto the register stack
F2XM1 Calculates 2x–1
FYL2X Calculates Y * log2 X
FYL2XP1 Calculates Y * log2 (X+1)
FPTAN Calculates the tangent of the value in ST
FPATAN Calculates the arctangent of the ratio Y/X
F[[N]]INIT, Resets the coprocessor and restores all the default conditions in the control and status words  
F[[N]]CLEX, Clears all exception flags and the busy flag of the status word  
FINCSTP Adds 1 to the stack pointer in the status word
FDECSTP Subtracts 1 from the stack pointer in the status word
FFREE Marks the specified register as empty

The following example illustrating several arithmetic instructions solves quadratic equations. It does no error checking and fails for some values because it attempts to find the square root of a negative number. You could revise the code using the FTST (Test for Zero) instruction to check for a negative number or 0 before the square root is calculated. If b2 - 4ac is negative or 0, the code can jump to routines that handle these two special cases.

.DATA

a REAL4 3.0

b REAL4 7.0

cc REAL4 2.0

posx REAL4 0.0

negx REAL4 0.0

.CODE

.

.

.

; Solve quadratic equation - no error checking

; The formula is: -b +/- squareroot(b2 - 4ac) / (2a)

fld1 ; Get constants 2 and 4

fadd st,st ; 2 at bottom

fld st ; Copy it

fmul a ; = 2a

fmul st(1),st ; = 4a

fxch ; Exchange

fmul cc ; = 4ac

fld b ; Load b

fmul st,st ; = b2

fsubr ; = b2 - 4ac

; Negative value here produces error

fsqrt ; = square root(b2 - 4ac)

fld b ; Load b

fchs ; Make it negative

fxch ; Exchange

fld st ; Copy square root

fadd st,st(2) ; Plus version = -b + root(b2 - 4ac)

fxch ; Exchange

fsubp st(2),st ; Minus version = -b - root(b2 - 4ac)

fdiv st,st(2) ; Divide plus version

fstp posx ; Store it

fdivr ; Divide minus version

fstp negx ; Store it

The examples in online help contain an enhanced version of this procedure.

6.2.4.3 Controlling Program Flow

The math coprocessors have several instructions that set control flags in the status word. The 8087-family control flags can be used with conditional jumps to direct program flow in the same way that 8086-family flags are used. Since the coprocessor does not have jump instructions, you must transfer the status word to memory so that the flags can be used by 8086-family instructions.

An easy way to use the status word with conditional jumps is to move its upper byte into the lower byte of the processor flags, as shown in this example:

fstsw mem16 ; Store status word in memory

fwait ; Make sure coprocessor is done

mov ax, mem16 ; Move to AX

sahf ; Store upper word in flags

The SAHF (Store AH into Flags) instruction in the example above transfers AH into the low bits of the flags register.

You can save several steps by loading the status word directly to AX on the 80287 with the FSTSW and FNSTSW instructions. This is the only case in which data can be transferred directly between processor and coprocessor registers, as shown in this example:

fstsw ax

The coprocessor control flags and their relationship to the status word are described in Section 6.2.4.4, “Control Registers.”

The 8087-family coprocessors provide several instructions for comparing operands and testing control flags. All these instructions compare the stack top (ST) to a source operand, which may either be specified or implied as ST(1).

The compare instructions affect the C3, C2, and C0 control flags, but not the C1 flag. Table 6.3 shows the flags set for each possible result of a comparison or test.

Table 6.3 Control-Flag Settings after Comparison or Test

After FCOM After FTEST C3 C2 C0

ST > source ST is positive 0 0 0
ST < source ST is negative 0 0 1
ST = source ST is 0 1 0 0
Not comparable ST is NAN or projective infinity , 1 1 1  

Variations on the compare instructions allow you to pop the stack once or twice and to compare integers and zero. For each instruction, the stack top is always the implied destination operand. If you do not give an operand, ST(1) is the implied source. With some compare instructions, you can specify the source as a memory or register operand.

All instructions summarized in the following list have implied operands: either ST as a single-destination operand or ST as the destination and ST(1) as the source. These are the instructions for comparing and testing flags.

Some instructions have a wait version and a no-wait version. The no-wait versions have N as the second letter.

Instruction Description

FCOM Compares the stack top to the source. The source and destination are unaffected by the comparison.
FTST Compares ST to 0.
FCOMP Compares the stack top to the source and then pops the stack.
FUCOM, FUCOMP, FUCOMPP Compare the source to ST and set the condition codes of the status word according to the result (80386/486 only).
F[[N]]STSW mem2byte, Stores the status word in memory.  
FXAM Sets the value of the control flags based on the type of the number in ST.
FPREM Finds a correct remainder for large operands. It uses the C2 flag to indicate whether the remainder returned is partial (C2 is set) or complete (C2 is clear). (If the bit is set, the operation should be repeated. It also returns the least-significant three bits of the quotient in C0, C3, and C1.)
FNOP Copies the stack top onto itself, thus padding the executable file and taking up processing time without having any effect on registers or memory.
FDISI, FNDISI, FENI, FNENI Enables or disables interrupts (8087 only).
FSETPM Sets protected mode. Requires a .286P or .386P directive (80287, 80387, and 80486 only).

The following example illustrates some of these instructions. Notice how conditional blocks are used to enhance 80287 code.

.DATA

down REAL4 10.35 ; Sides of a rectangle

across REAL4 13.07

diamtr REAL4 12.93 ; Diameter of a circle

status WORD ?

P287 EQU (@Cpu AND 00111y)

.CODE

.

.

.

; Get area of rectangle

fld across ; Load one side

fmul down ; Multiply by the other

; Get area of circle: Area = PI * (D/2)2

fld1 ; Load one and

fadd st, st ; double it to get constant 2

fdivr diamtr ; Divide diameter to get radius

fmul st, st ; Square radius

fldpi ; Load pi

fmul ; Multiply it

; Compare area of circle and rectangle

fcompp ; Compare and throw both away

IF p287

fstsw ax ; (For 287+, skip memory)

ELSE

fnstsw status ; Load from coprocessor to memory

mov ax, status ; Transfer memory to register

ENDIF

sahf ; Transfer AH to flags register

jp nocomp ; If parity set, can't compare

jz same ; If zero set, they're the same

jc rectangle ; If carry set, rectangle is bigger

jmp circle ; else circle is bigger

nocomp: ; Error handler

.

.

.

same: ; Both equal

.

.

.

rectangle: ; Rectangle bigger

.

.

.

circle: ; Circle bigger

Additional instructions for the 80387/486 are FLDENVD and FLDENVW for loading the environment; FNSTENVD, FNSTENVW, FSTENVD, and FSTENVW for storing the environment state; FNSAVED, FNSAVEW, FSAVED, and FSAVEW for saving the coprocessor state; and FRSTORD and FRSTORW for restoring the coprocessor state.

The size of the code segment, not the operand size, determines the number of bytes loaded or stored with these instructions. The instructions ending with W store the 16-bit form of the control register data, and the instructions ending with D store the 32-bit form. For example, in 16-bit mode FSAVEW saves the 16-bit control register data. If you need to store the 32-bit form of the control register data, use FSAVED.

6.2.4.4 Control Registers

Some of the flags of the seven 16-bit control registers control coprocessor operations, while others maintain the current status of the coprocessor. In this sense, they are much like the 8086-family flags registers (see Figure 6.8).

Of the control registers, only the status word register is commonly used (the others are used mostly by systems programmers). The format of the status word register is shown in Figure 6.9, which shows how the coprocessor control flags align with the processor flags. C3 overwrites the zero flag, C2 overwrites the parity flag, and C0 overwrites the carry flag. C1 overwrites an undefined bit, so it cannot be used directly with conditional jumps, although you can use the TEST instruction to check C1 in memory or in a register. The status word register also overwrites the sign and auxiliary-carry flags, so you cannot count on their being unchanged after the operation.