Extracting ROM constants from the 8087 math coprocessor's die
righto.com
righto.com
If an 8087 was present, the interrupt handler simply patched in place the INT instruction, replacing it with an 8087 instruction. If the math coprocessor was absent, instead, the interrupt handler decoded the subsequent instruction bytes from the instruction stream and performed software emulation. All this was needed because the 8088 and 8086 didn't have undefined opcode exceptions!
(This is separate from the constant ROM, which is a normal one-bit-per-transistor ROM.)
Just to highlight too that footnote 1, describing the lengths that the Intel engineers had to go to ensure that interaction between the 8086 and the 8087 worked, is fascinating.
I'm still investigating the chip, so I hope to find the exponent ROM (or Boolean logic as you suggest) and solve this puzzle.
This is history that deserves to be remembered.
Back in EE school in 1991, I installed an 8087 in my PC. The speed-up running Micro-CAP 3 was amazing, as a simple BJT CE amp sim only took a few minutes. Of course then I moved to a 486 and that same sim was finished before the mouse button lifted.
There has been an amazing amount of progress since then, but my PC is still too slow, as the simulations only get larger.
I don't know what ln(3) would be used for. My guess is it's used as somewhere in the algorithm to compute log2(x). Probably as a fixed point in CORDIC, or possibly as a boundary condition: if the intermediate value is above ln(3), do a bitshift, and add a constant to the result. But I don't know.
The fact that the x87 is a separate processor is a key part of what made Quake what it was. Because math on the FPU ran concurrently with the main CPU, you could do integer math physics calculations on the x86 and floating point math graphics calculations on the x87. So you'd have a significant speedup by interleaving everything together. Must have been a nightmare to write that code. Some x86 clones were clever, and used the same circuitry for everything. I think Cyrix chips for instance didn't separate the circuitry for x86 and x87 instructions. So x86 heavy code ran similarly between Cyrix and Intel, and x87 heavy code did also, but Quake (which combined x86 and x87 instructions relatively equally) ran like garbage on Cyrix chips. At the time, this gave Cyrix chips a terrible reputation. These days, Intel and AMD put fairly little effort into making x87 instructions fast, because code that cares about floating point performance use AVX instructions.
AFAIK x87 instructions were removed/deprecated in x86_64
SSE / AVX just performs so much better.
Precisely what it's good for.
Not necessarily. For example, the algorithm for 2^x with x between 0 and 1 just has to subtract the largest value in the table from x and perform the corresponding shift-and-add on the result. So for large enough x, they may have just done shift-by-2-and-add multiple times on the result, subtracting the corresponding log multiple times from the operand.
Though that still leaves the question of why they left out this particular table value and not any others. It may have been a balancing act of overall accuracy and performance vs table size vs microcode complexity.
Sorry, couldn’t resist. My brain did a thing.
> I'm a bit puzzled why the 8087 doesn't need the constant log2(1 + 2^-1)
log2(3/2) = log2(3) - log2(2) = log2(3) - 1
Problem... solved?
Your suggestion could apply to the ROM; they could have hard-coded the first bit to 1, saving a row in the ROM. I think they could have avoided a few transistors by doing this.
[...]
The chip's data path consists of 67 horizontal rows, so it seemed pretty clear that the 134 rows in the ROM corresponded to two sets of 67-bit constants. I extracted one set of constants for the odd rows and one for the even rows, but the values didn't make any sense. After more thought, I determined that the rows do not alternate but are arranged in a repeating "ABBA" pattern.7 Using this pattern yielded a bunch of recognizable constants, including pi and 1. Bits from those constants are shown in the diagram below. (In this photo, a 1 bit appears as a green stripe, while a 0 bit appears as a red stripe.) In binary, pi is 11.001001... and this value is visible in the upper labeled bits.
[...]
"The basic idea of CORDIC is to compute tangent and arctangent by breaking down an angle into smaller angles, and rotating a vector by these angles. The trick is that by carefully choosing the smaller angles, each rotation can be computed with efficient shifts and adds instead of trig functions. Specifically, suppose we want to find tan(z). We can break z into a sum of smaller angles: z ≈ {atan(2-1) or 0} + {atan(2-2) or 0} + {atan(2-3) or 0} + ... + {atan(2-16 or 0}. Now, rotating a vector by, say atan(2-2), can be done by multiplying by 2-2 and adding. The key thing is that multiplying by 2-2 is just a fast bit shift. Putting this all together, computing tan(z) can be done by comparing z with the atan constants, and then doing 16 cycles of additions and shifts, which are fast to perform in hardware.13 To make the algorithm work, the atan constants are precomputed and stored in the constant ROM.14
[...]
Some of the constants (such as pi) are expected, while others (such as log2(3)) are more puzzling."