CHAPTER 7   THE FLOATING-POINT PROCESSOR


In this chapter, we'll refer to the various Central Processing
Units (CPUs) as the "86".  Thus "86" refers to either the 8088,
8086, 80186, 80286, etc.  We'll refer to the various coprocessors
as the "87".  Thus "87" refers to either the 8087, the 287, the
387, or the special IIT-2C87 processor.


The 8087 and 287 Coprocessors

All IBM-PC's, and most clones, contain a socket for a floating
point coprocessor.  If you shell out between $80 and $300, and
plug the appropriate chip into that socket, then a host of
floating point instructions is added to the assembly language
instruction set.

The original IBM-PC, and the XT, accept the original floating
point chip, the 8087.  The AT accepts a later update, the 287.
From a programming standpoint, the two chips are nearly
identical: the 287 adds the instructions FSETPM and FSTSW AX, and
ignores the instructions FENI and FDISI.  There is, however, a
rather nasty design flaw in the 8087, that was corrected in the
287.

To understand the flaw, you must understand how the 86 and 87
work as coprocessors. Whenever the 86 sees a floating point
instruction, it communicates the instruction, and any associated
memory operands, to the 87.  Then the 86 goes on to its next
instruction, operating in parallel with the 87.  That's OK, so
long as the following instructions don't do one of the following:

  1. Execute another floating point instruction; or

  2. Try to read the results of the still-executing floating
     point instruction.

If they do, then you must provide an instruction called WAIT (or
synonymously FWAIT), which halts the 86 until the 87 is finished.
For almost all floating point instructions, it should not be
necessary to provide an explicit FWAIT; the 86 ought to know that
it should wait.  For the 8087, it IS necessary to give an
explicit FWAIT before each floating point instruction: that is
the flaw.

Because of the flaw, all assemblers supporting the 8087 will
silently insert an FWAIT code (hex 9B) before all 87
instructions, except those few (the FN instructions other than
FNOP) not requiring the FWAIT.  A86 provides the switch +F (the F
must be capitalized), to signal that the 287 is the target
processor.  A86 also provides the directive ".287", compatible
with Microsoft's assembler, that you can insert into your
programs to accomplish the same thing as +F. However, the actions
taken by A86 and Microsoft when seeing .287 are completely
disjoint!  To wit:
                                                              7-2

* A86 ceases outputting FWAIT directives that are unnecessary for
  the 287.  For reasons beyond my comprehension, Microsoft
  continues to put them out.  Can someone enlighten me as to why
  Microsoft is putting out those codes?

* A86 ignores the instructions FENI, FDISI, FNENI, and FNDISI
  after it sees a .287 directive.  Microsoft continues to
  assemble these instructions.

* Microsoft recognizes the new 287 instructions, if and only if
  it sees the .287 directive.  A86 recognizes them even if .287
  is not given.  In general, I don't attempt to police your
  instruction usage-- if you use an instruction available on a
  limited number of processors, I trust that you are programming
  for one of those processors.

In summary, if your program will be running only on machines with
a 287, you can give ".287" directive. Your programs will be
significantly shorter than if they were assembled by Microsoft.
If you want your programs to run on all machines containing a
floating point chip, you should refrain from specifying .287.

WARNING: The most common mistake 87 programmers make is to try to
read the results of an 87 operation in 86 memory, before the
results are ready.  At least on my AT, the system often crashes
when you do this!  If your program runs correctly when single
stepped, but crashes when set loose, then chances are you need an
extra explicit FWAIT somewhere.


Extra Coprocessor Support

A86 now supports two additional coprocessors available for
PC-compatibles: the 80387, available for 386-based machines, and
the IIT-2C87, a 287-plug-compatible chip that adds a couple of
unique instructions. The IIT-2C87 has two extra banks of on-chip
8-number stacks, that can be switched in with the FBANK
instruction, and a matrix multiply instrction that uses all three
banks as input.  (For details contact Specialty Software
Development Corp., 110 Wild Basin Road, Austin TX 78746.) Both
chips incorporate the correction to the 8087's FWAIT design flaw,
so you can assemble with the .287 directive.  The extra
instructions for these chips are marked by "387 only:" and "IIT
only:" in the chart at the end of this chapter.


Emulating the 8087 by Software

There is a software package provided with many compilers
(Borland's Turbo C and most Microsoft compilers, for example)
that emulates the 8087 instruction set.  The emulator is very
cleverly implemented so that the programmer need not know whether
a floating point chip will be available, or whether emulation
will be necessary.  This is done by having the linker replace all
floating point machine instructions with INT calls to certain
interrupts, dedicated to emulation.  The interrupt handlers
interpret the operands to the instructions, and emulate the 8087.
                                                              7-3

You can tell A86 that the emulator might be used, by providing a
+f switch in the invocation line, or in the A86 environment
variable (make sure the f is lower case).  Since your program
will be linked to the emulator, you must be producing an OBJ
file, not a COM file, for emulation support to take effect.
Whenever a floating point instruction is assembled, A86 will
generate an external reference at the opcode for the instruction.
Then, if the emulation package is linked with your program, the
opcodes will be replaced by the INT calls. If a special
non-emulation module is linked, the opcodes will be left alone,
and the floating point instructions will be executed directly.


The Floating Point Stack

The 87 has its own register set, of 8 floating point numbers
occupying 10 bytes each, plus 14 bytes of status and control
information.  Many of the 87's instructions cause the numbers to
act like a stack, much like a Hewlett-Packard calculator.  For
this reason, the numbers are called the floating point stack.

The standard name for the top element of the floating point stack
is either ST or ST(0); the others are named ST(1) through ST(7).
Thus, for example, the instruction to add stack element number 3
into the top stack element is usually coded FADD ST,ST(3).

I find this notation painfully verbose.  Especially bad are the
parentheses, which are hard to type, and which add visual clutter
to the program.  To alleviate this problem while retaining
language compatibility, I name my stack elements simply 0 through
7.  I recognize ST as a synonym for 0.  I allow expression
elements to be concatenated; concatenation is the same as
addition.  Thus, when A86 sees ST(3), it computes 0+3 = 3.  So
you can code the old way, FADD ST,ST(3), or you can code the
concise way, FADD 0,3 or simply FADD 3.


Floating Point Initializations

In general, you use the 87 by loading numbers from 86 memory to
the 87 stack (using FLD instructions), calculating on the 87
stack, and storing the results back to 86 memory (using FST and
FSTP instructions).  There are seven constant numbers built into
the 87 instruction set: zero, one, Pi, and four logarithmic
conversion constants.  These can be loaded using the FLD0, FLD1,
FLDPI, FLDL2T, FLDL2E, FLDLG2, and FLDLN2 instructions.  All
other constants must be declared in, then loaded from, 86 memory.
Integer constant words and doublewords can be loaded via FILD.
Non-integer constant doubleword, quadwords, and ten-byte numbers
can be loaded via FLD.
                                                              7-4

A86 allows you to declare constants loaded via FLD as floating
point numbers, using scientific notation if you like.  As an
exclusive feature, A86 allows you to use any of the 4 arithmetic
functions +, -, *, / in expressions involving floating point
numbers.  A86 will even do type conversion if one of the two
operands is given as an integer; though for clarity I recommend
that you always give floating point constants with their decimal
point.


Built-In Constant Names

A86 offers another exclusive feature: the built-in symbols

    PI   ratio of circumference to diameter of a circle

    L2T  log base 2 of 10

    L2E  log base 2 of the calculus constant e = 2.71828...

    LG2  log base 10 of 2

    LN2  natural log (base e) of 2

You can use these symbols in expressions, to declare useful
constants.  For example, you can declare the degrees-to-radians
conversion constant:

    DEG_TO_RAD  DT  PI/180.



Special Immediate FLD Form

Yet another exclusive A86 feature is the instruction form FLD
constant.  This form is intended primarily to facilitate "fooling
around" with the 87 when using D86; but it is also useful for
quick-and-dirty programs.  For example, the instruction FLD 12.3
generates the following sequence of code bytes (without
explicitly using the local labels given):

    CS FLD T[M1]
    JMP >M2
  M1  DT 12.3
  M2:

Obviously, this form is not terrifically efficient: you can
always save the JMP by placing the constant outside of the
instruction stream; and the CS override might not be needed.  But
the form is very, very convenient!

NOTE that the preceding 2 sections imply that you can get
careless and code, for example, FLD PI when you intended FLDPI.
Though the two are functionally equivalent, the first form takes
a whopping 17 bytes; and second, only 2 bytes.  Be careful!
                                                              7-5

Floating Point Operand Types

The list of floating point instructions contains a variety of
operand types.  Here is a brief explanation of those types:

0        stands for the top element of the floating point stack.
         A synonym for 0 is ST or ST(0).

i        stands for element number i of the floating point stack.
         i can range from 0 through 7.  A synonym for i is ST(i).

mem10r   is a 10-byte memory quantity (typically declared with a
         DT directive) containing a full precision floating point
         number. Intel recommends that you NOT store your numbers
         in full precision; that you use the following double
         precision format instead.  Full precision numbers are
         intended for storage of intermediate results (on the
         stack); they exist to insure maximum accuracy for
         calculations on double precision numbers, which is the
         official external format of 87 numbers.

mem8r    is an 8-byte memory quantity (typically declared with a
         DQ directive) containing a double precision floating
         point number.  This is the best format for floating
         point numbers on the 87.  The 87 takes the same amount
         of time on double precision calculations as it does on
         single precision.  The only extra time is the memory
         access of 4 more bytes; negligible in comparison to the
         calculation time.

mem4r    is a 4-byte quantity (typically defined with a DD
         directive) containing a single precision floating point
         number.

mem10d   is a 10-byte quantity (also defined via DT) containing a
         special Binary Coded Decimal format recognized by the
         FBLD and FBSTP instructions.  This format is useful for
         input and output of floating point numbers.

mem4i    is a 4-byte quantity representing a signed integer in
         two's-complement notation.

mem2i    is a 2-byte quantity representing a signed integer in
         two's-complement notation.

mem14    and mem94 are 14- and 94-byte buffers containing the 87
         machine state.
                                                              7-6

Operand Choices in A86

In the "standard" assembly language, the choice of operands for
floating point instructions seems inconsistent to me.  For
example, to subtract stack i from 0, you must provide two
operands; to do the equivalent comparison, you must provide only
one operand.  A86 smooths out these inconsistencies by allowing
more choices for operands: FADD i is equivalent to FADD 0,i. FCOM
0,i is equivalent to FCOM i.  The same holds for the other main
arithmetic instructions.  FXCH 0,i and FXCH i,0 are allowed. So
if you wish to retain compatibility with other assemblers, you
should use their more restrictive instruction list, not the
following one.


The 87 Instruction Set

Following is the 87 instruction set.  The "w" in the opcode field
is the FWAIT opcode, hex 9B, which is suppressed if .287 is
selected.  Again, "0", "1", and "i" stand for the associated
floating point stack registers, not constant numbers!  Constant
numbers in the descriptions are given with decimal points: 0.0,
1.0, 2.0, 10.0.



    Opcode    Instruction     Description

 w  D9 F0     F2XM1           0 := (2.0 ** 0) - 1.0
 w  DB F1     F4X4            IIT only: 4 by 4 matrix multiply
 w  D9 E1     FABS            0 := |0|
 w  DE C1     FADD            1 := 1 + 0, pop
 w  D8 C0+i   FADD i          0 := i + 0
 w  DC C0+i   FADD i,0        i := i + 0
 w  D8 C0+i   FADD 0,i        0 := i + 0
 w  D8 /0     FADD mem4r      0 := 0 + mem4r
 w  DC /0     FADD mem8r      0 := 0 + mem8r
 w  DE C0+i   FADDP i,0       i := i + 0, pop
 w  DB E8     FBANK 0         IIT only: set bank pointer to default
 w  DB EB     FBANK 1         IIT only: set bank pointer to bank 1
 w  DB EA     FBANK 2         IIT only: set bank pointer to bank 2
 w  DF /4     FBLD mem10d     push, 0 := mem10d
 w  DF /6     FBSTP mem10d    mem10d := 0, pop
                                                              7-7

 w  D9 E0     FCHS            0 := -0
9B  DB E2     FCLEX           clear exceptions
 w  D8 D1     FCOM            compare 0 - 1
 w  D8 D0+i   FCOM 0,i        compare 0 - i
 w  D8 D0+i   FCOM i          compare 0 - i
 w  D8 /2     FCOM mem4r      compare 0 - mem4r
 w  DC /2     FCOM mem8r      compare 0 - mem8r
 w  D8 D9     FCOMP           compare 0 - 1, pop
 w  D8 D8+i   FCOMP 0,i       compare 0 - i, pop
 w  D8 D8+i   FCOMP i         compare 0 - i, pop
 w  D8 /3     FCOMP mem4r     compare 0 - mem4r, pop
 w  DC /3     FCOMP mem8r     compare 0 - mem8r, pop
 w  DE D9     FCOMPP          compare 0 - 1, pop both
 w  D9 FF     FCOS            387 only: push, 1/0 := cosine(old 0)

 w  D9 F6     FDECSTP         decrement stack pointer
 w  DB E1     FDISI           disable interrupts (.287 ignore)

 w  DE F9     FDIV            1 := 1 / 0, pop
 w  D8 F0+i   FDIV i          0 := 0 / i
 w  DC F8+i   FDIV i,0        i := i / 0
 w  D8 F0+i   FDIV 0,i        0 := 0 / i
 w  D8 /6     FDIV mem4r      0 := 0 / mem4r
 w  DC /6     FDIV mem8r      0 := 0 / mem8r

 w  DE F8+i   FDIVP i,0       i := i / 0, pop
 w  DE F1     FDIVR           1 := 0 / 1, pop
 w  D8 F8+i   FDIVR i         0 := i / 0
 w  DC F0+i   FDIVR i,0       i := 0 / i
 w  D8 F8+i   FDIVR 0,i       0 := i / 0
 w  D8 /7     FDIVR mem4r     0 := mem4r / 0
 w  DC /7     FDIVR mem8r     0 := mem8r / 0
 w  DE F0+i   FDIVRP i,0      i := 0 / i, pop

 w  DB E0     FENI            enable interrupts (.287 ignore)
 w  DD C0+i   FFREE i         empty i
 w  DE /0     FIADD mem2i     0 := 0 + mem4i
 w  DA /0     FIADD mem4i     0 := 0 + mem2i
 w  DE /2     FICOM mem2i     compare 0 - mem2i
 w  DA /2     FICOM mem4i     compare 0 - mem4i
 w  DE /3     FICOMP mem2i    compare 0 - mem2i, pop
 w  DA /3     FICOMP mem4i    compare 0 - mem4i, pop

 w  DE /6     FIDIV mem2i     0 := 0 / mem2i
 w  DA /6     FIDIV mem4i     0 := 0 / mem4i
 w  DE /7     FIDIVR mem2i    0 := mem2i / 0
 w  DA /7     FIDIVR mem4i    0 := mem4i / 0
 w  DF /0     FILD mem2i      push, 0 := mem2i
 w  DB /0     FILD mem4i      push, 0 := mem4i
 w  DF /5     FILD mem8i      push, 0 := mem8i
                                                              7-8

 w  DE /1     FIMUL mem2i     0 := 0 * mem2i
 w  DA /1     FIMUL mem4i     0 := 0 * mem4i
 w  D9 F7     FINCSTP         increment stack pointer
9B  DB E3     FINIT           initialize 87
 w  DF /2     FIST mem2i      mem2i := 0
 w  DB /2     FIST mem4i      mem4i := 0
 w  DF /3     FISTP mem2i     mem2i := 0, pop
 w  DB /3     FISTP mem4i     mem4i := 0, pop
 w  DF /7     FISTP mem8i     mem8i := 0, pop

 w  DE /4     FISUB mem2i     0 := 0 - mem2i
 w  DA /4     FISUB mem4i     0 := 0 - mem4i
 w  DE /5     FISUBR mem2i    0 := mem2i - 0
 w  DA /5     FISUBR mem4i    0 := mem4i - 0



 w  D9 C0+i   FLD i           push, 0 := old i
 w  DB /5     FLD mem10r      push, 0 := mem10r
 w  D9 /0     FLD mem4r       push, 0 := mem4r
 w  DD /0     FLD mem8r       push, 0 := mem8r
 w  D9 E8     FLD1            push, 0 := 1.0
 w  D9 /5     FLDCW mem2i     control word := mem2i
 w  D9 /4     FLDENV mem14    environment := mem14
 w  D9 EA     FLDL2E          push, 0 := log base 2.0 of e
 w  D9 E9     FLDL2T          push, 0 := log base 2.0 of 10.0
 w  D9 EC     FLDLG2          push, 0 := log base 10.0 of 2.0
 w  D9 ED     FLDLN2          push, 0 := log base e of 2.0
 w  D9 EB     FLDPI           push, 0 := Pi
 w  D9 EE     FLDZ            push, 0 := +0.0

 w  DE C9     FMUL            1 := 1 * 0, pop
 w  D8 C8+i   FMUL i          0 := 0 * i
 w  DC C8+i   FMUL i,0        i := i * 0
 w  D8 C8+i   FMUL 0,i        0 := 0 * i
 w  D8 /1     FMUL mem4r      0 := 0 * mem4r
 w  DC /1     FMUL mem8r      0 := 0 * mem8r
 w  DE C8+i   FMULP i,0       i := i * 0, pop

    DB E2     FNCLEX          nowait clear exceptions
    DB E1     FNDISI          disable interrupts (.287 ignore)
    DB E0     FNENI           enable interrupts (.287 ignore)
    DB E3     FNINIT          nowait initialize 87
 w  D9 D0     FNOP            no operation

    DD /6     FNSAVE mem94    mem94 := 87 state
    D9 /7     FNSTCW mem2i    mem2i := control word
    D9 /6     FNSTENV mem14   mem14 := environment
    DF E0     FNSTSW AX       AX := status word
    DD /7     FNSTSW mem2i    mem2i := status word
 w  D9 F3     FPATAN          0 := arctan(1/0), pop
 w  D9 F8     FPREM           0 := REPEAT(0 - 1)
 w  D9 F5     FPREM1          387 only: 0 := REPEAT(0 - 1) IEEE compat.
 w  D9 F2     FPTAN           push, 1/0 := tan(old 0)
                                                              7-9

 w  D9 FC     FRNDINT         0 := round(0)
 w  DD /4     FRSTOR mem94    87 state := mem94
 w  DD /6     FSAVE mem94     mem94 := 87 state
 w  D9 FD     FSCALE          0 := 0 * 2.0 ** 1
9B  DB E4     FSETPM          set protection mode
 w  D9 FE     FSIN            387 only: push, 1/0 := sine(old 0)
 w  D9 FB     FSINCOS         387 only: push, 1 := sine, 0 := cos(old 0)
 w  D9 FA     FSQRT           0 := square root of 0

 w  DD D0+i   FST i           i := 0
 w  D9 /2     FST mem4r       mem4r := 0
 w  DD /2     FST mem8r       mem8r := 0
 w  D9 /7     FSTCW mem2i     mem2i := control word
 w  D9 /6     FSTENV mem14    mem14 := environment
 w  DD D8+i   FSTP i          i := 0, pop
 w  DB /7     FSTP mem10r     mem10r := 0, pop
 w  D9 /3     FSTP mem4r      mem4r := 0, pop
 w  DD /3     FSTP mem8r      mem8r := 0, pop
 w  DF E0     FSTSW AX        AX := status word
 w  DD /7     FSTSW mem2i     mem2i := status word

 w  DE E9     FSUB            1 := 1 - 0, pop
 w  D8 E0+i   FSUB i          0 := 0 - i
 w  DC E8+i   FSUB i,0        i := i - 0
 w  D8 E0+i   FSUB 0,i        0 := 0 - i
 w  D8 /4     FSUB mem4r      0 := 0 - mem4r
 w  DC /4     FSUB mem8r      0 := 0 - mem8r
 w  DE E8+i   FSUBP i,0       i := i - 0, pop
 w  DE E1     FSUBR           1 := 0 - 1, pop
 w  D8 E8+i   FSUBR i         0 := i - 0
 w  DC E0+i   FSUBR i,0       i := 0 - i
 w  D8 E8+i   FSUBR 0,i       0 := i - 0
 w  D8 /5     FSUBR mem4r     0 := mem4r - 0
 w  DC /5     FSUBR mem8r     0 := mem8r - 0
 w  DE E0+i   FSUBRP i,0      i := 0 - i, pop

 w  D9 E4     FTST            compare 0 - 0.0
 w  DD E0+i   FUCOM i         387 only: unordered compare 0 - i
 w  DD E1     FUCOM           387 only: unordered compare 0 - 1
 w  DD E8+i   FUCOMP i        387 only: unordered compare 0 - i, pop
 w  DD E9     FUCOMP          387 only: unordered compare 0 - 1, pop
 w  DA E9     FUCOMPP         387 only: unordered compare 0 - 1, pop both
9B            FWAIT           wait for 87 ready
 w  D9 E5     FXAM            C3 -- C0 := type of 0
 w  D9 C9     FXCH            exchange 0 and 1
 w  D9 C8+i   FXCH 0,i        exchange 0 and i
 w  D9 C8+i   FXCH i          exchange 0 and i
 w  D9 C8+i   FXCH i,0        exchange 0 and i
 w  D9 F4     FXTRACT         push, 1 := expo, 0 := sig
 w  D9 F1     FYL2X           0 := 1 * log base 2.0 of 0, pop
 w  D9 F9     FYL2XP1         0 := 1 * log base 2.0 of (0+1.0), pop

