38

The 8087 has many instructions - too many, it seems, to be encoded as part of the 8086 instruction set. How did the Intel 8086 interface with an Intel 8087 FPU that a user added?

Consider the following x86 assembly code sample:

// c = a + b;
fld    DWORD PTR [rbp-0xc]  // a;
fadd   DWORD PTR [rbp-0x8]  // b;
fstp   DWORD PTR [rbp-0x4]  // c;

The instructions fld, fadd, and ftsp I assume are not hardwired into the 8086 circuit. So are they pseudo-instructions that the assembler subsequently converts to command/data instructions for the 8086 to pass onto the 8087 appropriately?

For example fld might be encoded as command 0 and for data the value of rbp-0xc is encoded which the 8087 would know is an address in memory holding the value it needs? And then a sequence of OUT instructions are used by the 8086 to send the command and data to the 8087?

psmears
  • 173
  • 4
Jet Blue
  • 1,995
  • 3
  • 18
  • 25
  • Related question on StackOverflow: https://stackoverflow.com/questions/42543905/what-are-8086-esc-instruction-opcodes –  Feb 13 '19 at 01:21

1 Answers1

49

The opcodes in your list are all only 16 bits (plus the extra bytes for address calculation) and you'll notice that they all begin (in hex) with Dx where x >= 8. This is because, to the 8086, any instruction whose first byte has the bit pattern 11011xxx was deemed to be an 8087 coprocessor instruction.

When the 8086 encountered a floating point opcode, it would do all the stuff to calculate the effective address and fetch the byte at that address and then it would just carry on.

Meanwhile the 8087 reads all the same instructions as the 8086 and when it encountered an instruction destined for it (i.e. that started with 11011xx), it would simply read the data bus at the right time to get the first byte i.e. when the 8086 was fetching the data. For multi-byte reads it would also read the address bus and then take control of the memory bus after the 8086 read cycle and read the remaining bytes pointed to by the address using direct memory access (DMA). For writes, it would ignore the data bus, read the address and then use DMA to write the data.

The only direct connections between the 8086 and 8087 were a few control lines, some to synchronise the prefetch queues of the 8086 and the 8087 - so the 8087 would know exactly when the 8086 was executing floating point instructions - and one so that the 8086 could tell when the 8087 had finished the last instruction and was ready for the next. There was a special instruction in the 8086 called wait that would simply cause the 8086 to wait until the 8087 signalled it was not busy on this line.

JeremyP
  • 11,631
  • 1
  • 37
  • 53
  • 2
    Maybe worth mentioning that some mnemonics for instructions that read x87 state bake in a wait. e.g. fstsw [bp-4] (store the status word, e.g. after an FP compare; then you'd load it into AX and use lahf to set up for a jp (unordered) or ja/jb or whatever). The manual https://www.felixcloutier.com/x86/fstsw:fnstsw lists the machine code as 9B DD /7 m2byte, where the 9B is a fwait instruction and the DD /7 is the actual fnstsw. (Yes there are fn... mnemonics for the no-wait version. On modern x86 with integrated x87 you don't need the fwait.) – Peter Cordes Feb 13 '19 at 07:13
  • 2
    @Peter re the last point, IIRC FWAIT isn’t necessary since the 286 (which checks its TEST line before executing an NPX instruction). Most assemblers will omit FWAITs if .287 or later is specified. – Stephen Kitt Feb 13 '19 at 08:45
  • 7
    11011 is the ESC (escape) instruction prefix, of which coprocessor instructions are a subset. The second byte of the instruction starts with 11 if it’s a non-memory instruction, and the usual mode field if it isn’t — that’s how the 8086 knows how to handle the instruction without knowing what it does, or who handles it. – Stephen Kitt Feb 13 '19 at 08:50
  • The question is 8086-specific, later CPUs changed things to various extents (a little on the 286, and a lot on the 386). – Stephen Kitt Feb 13 '19 at 08:51
  • @StephenKitt: NASM and YASM don't, even when assembling fstsw ax for x86-64. Even if you specify CPU pentium or something. When you say "most", I think you're talking about assemblers that use MASM/TASM style syntax. (Which people used when x87 was most relevant.) GAS (from GNU binutils) is the same. If you want fnstsw with those assemblers, that's what you have to write. :/ But fortunately with "modern" x87 code (P6 and later), you'd normally use FCOMI[P] which sets EFLAGS directly, so normal compiler-generated x87 code isn't wasting code size. – Peter Cordes Feb 13 '19 at 08:55
  • @Peter indeed, “most” referred to the assemblers in use back then ;-). I haven’t tried writing FPU code with NASM or YASM... – Stephen Kitt Feb 13 '19 at 08:56
  • With something like gcc -O3 -m32 -march=i386, gcc emits fnstsw ax. https://godbolt.org/z/kwIayO. So gcc knows about this, and uses the n version of the mnemonic manually. – Peter Cordes Feb 13 '19 at 08:57
  • @Peter to my mind NASM & co. are much more literal assemblers than TASM and especially MASM, which would explain the difference in handling too. My point was more about the 8086/8087 pair being the only setup where FWAIT is necessary (it’s not related to the integration or otherwise of the FPU). – Stephen Kitt Feb 13 '19 at 09:00
  • @StephenKitt Were there other coprocessors at that time, maybe for a completely different purpose? Or were they planned? – glglgl Feb 13 '19 at 09:45
  • 2
    @glglgl there were, see this question. – Stephen Kitt Feb 13 '19 at 10:02
  • I was always told that x87 processors simply disabled the x86 ones and took over. This didn't make much sense to me. Glad to find out this isn't the case. – hjf Feb 13 '19 at 15:50
  • 2
    @hjf That is true for 486SX and 487. The latter is a fully-functional 486DX. But for 8086/7, no, that's not the case. – void_ptr Feb 13 '19 at 16:43
  • @StephenKitt: As far as I understand the answers to that question, they didn't identify any other coprocessor that used ESC instruction encodings to interface with the main CPU. – hmakholm left over Monica Feb 13 '19 at 17:29
  • @Henning I’m not sure how the 8089 interfaced with the 8086, but yes, it’s quite possible that nothing apart from x87 used the ESC encodings. – Stephen Kitt Feb 13 '19 at 18:17
  • @StephenKitt: Wikipedia provides a link to a scanned 8089 data sheet. Definitely doesn't use ESC instructions. – hmakholm left over Monica Feb 14 '19 at 04:04
  • @StephenKitt The 8089 Assembler Guide says that the 8089 has its own instruction set and the CPU communicates through a mutually designated block of memory. There are no special 8086 instructions to talk to it. – JeremyP Feb 14 '19 at 09:53
  • @JeremyP right, I’d read that too, I wondered if there was anything more given Wikipedia’s mention that “It used the same programming technique as 8087 for input/output operations”. If any synchronisation is needed, I imagine WAIT is sufficient, and that’s not an ESC instruction. – Stephen Kitt Feb 14 '19 at 10:05
  • @StephenKitt there are flags in the control block for synchronisation. Additionally, the 8089 can raise an interrupt on the 8086. – JeremyP Feb 14 '19 at 10:16
  • 1
    the fact that x87 instructions begin with Dx has been utilized for emulating floating-point operations. What is the protocol for x87 floating point emulation in MS-DOS? – phuclv Feb 16 '19 at 03:25