Skip to content
HN On Hacker News ↗

Inside the Intel 8087 chip: floating-point addition and subtraction

▲ 128 points • 35 comments • by pwg • 3w ago • HN discussion ↗

Pangram verdict · v3.3

We believe that this entire text is human-written.

0 %

AI likelihood · overall

Human
100% human-written 0% AI-generated
SEGMENTS · HUMAN 1 of 1
SEGMENTS · AI 0 of 1
WORD COUNT 1,611
PEAK AI % 0% · §1
Analyzed
Oct 18
backend: pangram/v3.3
Segments scanned
1 windows
avg 1611 words each
Distribution
100 / 0%
human / AI fraction
Verdict
Human
Pangram v3.3

Article text · 1,611 words · 1 segments analyzed

Human AI-generated
§1 Human · 0%

In 1980, Intel introduced the 8087, a chip that made floating-point operations much faster in the IBM PC and other systems. Although the 8087 supports advanced operations such as tangents, square roots, and logarithms, this article focuses on basic arithmetic operations: addition and subtraction. The 8087 provided a spectacular speed improvement over the 8086 processor, speeding up addition by a factor of almost 100, and complicated operations by even more.1 In this article, I examine the 8087's microcode and circuitry to explain exactly how floating-point addition and subtraction are implemented, including numerous special cases for infinities and other special values. To explore the 8087's circuitry, I popped the lid off a chip with a chisel and used a microscope to create the high-resolution image below. The microcode ROM is the large rectangular region in the center of the die, holding the 1648 micro-instructions that control the chip. The bottom half of the chip (red box) is the datapath, the circuitry that performs floating-point calculations on 80-bit values.2 A close-up of the 8087's datapath, showing functional blocks that are used for addition and subtraction. Click this image (or any other) for a larger version. Zooming in on the datapath shows the functional blocks that are important for addition and subtraction. The key component is the integer adder, located in the center. It receives one argument from the B register and another argument from a component such as the temporary registers or the stack registers. The adder's output is stored in the sum register. The shifter can shift the bits in a value to the left or the right. The shifter occupies a lot of space on the die because its circuitry allows a value to be rapidly shifted by an arbitrary number of bits. Floating-point operations make heavy use of the shifter to align values; the leading zero circuitry counts the number of zero bits or bytes on the left of a value so they can be shifted away. The exponent converter processes exponents, detecting overflows and underflows. These specialized circuits helped the 8087 achieve its high performance. Most computers use integers as their basic representation for numbers. But many applications need to use numbers with a fractional part, such as 1.23. Moreover, integers have a very limited range: a 32-bit integer can express numbers from about -2 billion to 2 billion. To solve these problems,3 computers usually implement floating-point numbers, which provide fractional numbers over an enormous range. In most cases, a floating-point number is represented by sign × significand × 2exponent. The significand is a binary number of the form 1.bbb…: a leading 1, followed by the binary point (the binary equivalent of the decimal point) and the rest of the bits.4 In a binary number, each bit represents a power of 2. For the fractional part, the part after the binary point, the powers of 2 are negative: 2-1, 2-2, and so forth. So the binary number 1.1 is 1 + 1/2, or 1.5 in decimal. Similarly, binary 1.0011 is 1 + 1/8 + 1/16 or 1.1875. The exponent allows numbers to escape the limited range of the significand. For instance, 1.1 (binary) × 28 is 304. With larger exponents, floating-point numbers can cover a huge range; even a 32-bit short floating-point number covers a range exceeding 10-36 to 1038. Floating-point addition and subtraction are more complicated than integer operations. Before the numbers can be added (or subtracted), their binary points must be aligned, the same as aligning the decimal points for decimal numbers. After the operation, the exponent of the result must be adjusted, normalizing the significand so it starts with a 1 bit. The steps for a simple floating-point addition. The diagram above illustrates a simple floating-point addition: 13.5+3.5. The first two lines show the two arguments expressed with an exponent and significand. Both arguments are normalized, so the leftmost bit is a 1. Before adding, the second argument must be aligned—making the exponents equal—by shifting it two positions to the right. (The difference between the exponents in this case is two.) The values are then added, using integer addition on the aligned significands. In this case, adding the values causes an overflow, an "extra" bit on the left. The result must be normalized by shifting it one position to the right and incrementing the exponent by 1. Floating-point subtraction is similar, but has a few more complications. The result can be positive, negative, or zero. The result can be orders of magnitude smaller than the arguments. The figure below shows a basic subtraction, 8-7.5. As with addition, the second argument is shifted right so the exponents match (i.e., the binary points line up). The two significands are subtracted with integer subtraction. The result is normalized by shifting to the left so the top bit is a 1. Unlike the addition case, normalization may require a large shift. In this case, the shift is four bits and the exponent is decremented by 4 to yield the result. A simple floating-point subtraction. One complication with subtraction is that the result can be negative. If that occurs, the significand is made positive, and the sign of the result is flipped. Subtraction can also result in zero. In this case, normalization cannot be performed; instead, a special value +0 or -0 is returned as appropriate. Relevant hardware details of the 8087 In this section, I'll explain some features of the 8087 that play important roles in the microcode implementation of addition and subtraction. The 8087 supports a variety of data types, but internally, the 8087 stores numbers in an 80-bit format called "temporary real". A temporary real number has a sign bit, a 15-bit exponent, and a 64-bit significand. The 15-bit exponent allows numbers that are absurdly large, over 104932. The 64-bit significand supports extremely accurate numbers. The exponent is stored with a "bias" of 16383 added to it. Thus, the stored exponent is always positive, even if the real exponent is negative. For instance, an exponent of 1 is stored as 0x4000, and an exponent of -16 is stored as 0x3fef. The 80-bit temporary real format and the register formats. The dot indicates the binary point, analogous to the decimal point. The 8087 has eight internal registers, organized as a stack. The programmer uses these registers when programming the 8087. Each register holds an 80-bit floating-point number. To optimize performance, each value in the register stack also has an associated "tag" value: zero, valid, special, or empty. A value of zero is tagged as zero. A "normal" floating-point value is tagged as valid. If the value is infinity, Not a Number (NaN), or a denormalized value, then it is tagged as special. Finally, an empty tag indicates that a register does not hold a value; this allows detection of stack underflow (reading an empty register) or stack overflow (storing to a non-empty register). The 8087 also has temporary registers that it uses internally. tmpA and tmpB are 80-bit registers, along with two tag bits. However, tmpC is different: it doesn't have a sign, exponent, or tag. Moreover, the significand is 68 bits; tmpC has an additional bit to prevent overflow and three additional low-order bits for rounding, known as guard, round, and sticky. In the microcode, each 16-bit micro-instruction performs one low-level operation. Many of the operations move data from one register to another. Other operations provide conditional jumps, subroutine calls, and returns. The heart of the 8087's processing is the 69-bit integer adder, which I discussed here. (The adder has more than 64 bits because it has three rounding bits and a couple of additional high-order bits for overflow and multiplication.) Integer addition takes two micro-instructions. The first micro-instruction specifies the first argument to add and selects various options such as carry and rounding. (For subtraction, an option complements the second argument.) The second argument to the adder always comes from the B register, which confusingly is unrelated to the tmpB register. The result of the addition operation is placed in a special register called the sum register. A second micro-instruction (which may be many instructions after the first) moves the result from the sum register to a destination. Floating-point also makes extensive use of the bit shifter, which shifts a 68-bit value by an arbitrary number of positions, left or right; I wrote about it here. Like the adder, a bit shift operation takes two micro-instructions: the first moves a value to the shifter and configures the direction and amount of shift. The second micro-instruction moves the result from the shifter to a destination. The addition/subtraction microcode The 8087 implements almost all of its operations in microcode. The basic math operations (addition, subtraction, multiplication, and division) start by executing the same microcode (at decimal address #0001. The microcode moves the two arguments into temporary registers tmpA and tmpB. Normally, these arguments are taken from the stack registers. The arguments can also be taken from memory, but I won't discuss that here. Next, the microcode does a micro-subroutine call to handle the specific operation type. The 8087's microcode engine has a tiny ROM that holds a table of addresses, targets for microcode jumps or subroutine calls. Target 0 is special, however; it has three different addresses for multiplication, division, or addition/subtraction. (This is implemented by special-case circuitry in the ROM. When you're designing the chip, you can do weird, hacky optimizations like this if you want.) The addition/subtraction micro-subroutine starts at address #0232; this microcode is shown below. The control flow is complicated, so I've put a flowchart in the footnotes.5 The microcode first handles the zero cases 0+0 or 0-0. Next, it subtracts the exponents of the