Microcode in Intel's 8087 floating-point chip: the scale instruction

Sep 12, 2026 10:49 PM - 2 days ago 4

In the 1970s, floating-point arithmetic was a mess. Computer manufacturers had a twelve incompatible arithmetic standards. Moreover, floating-point systems were designed astir hardware simplicity alternatively than mathematical rigor, starring to problems with numerical stability. This changed erstwhile Intel introduced the 8087 floating-point coprocessor spot successful 1980, designed to beryllium arsenic meticulous arsenic possible, moreover successful the area cases. The 8087 became celebrated because it could beryllium installed successful the IBM PC, making floating-point operations up to 100 times faster successful applications ranging from spreadsheets to CAD. But much importantly, the 8087 became the floating-point modular utilized by astir computers today.

The 8087 implemented its instructions successful analyzable low-level codification called microcode. I'm portion of a group, the Opcode Collective, that is reverse-engineering this microcode, and I've precocious made immoderate progress. In this post, I analyse the microcode for 1 of the 8087's instructions—FSCALE—and picture really this microcode works. The FSCALE (Floating-point Scale) instruction provides a speedy measurement to standard a number by a powerfulness of two, overmuch faster than a multiplication. I figured that FSCALE was a simple, almost trivial instruction that would beryllium straightforward to understand and explain. Spoiler: it is not simple. FSCALE uses complete 140 micro-instructions and 3 levels of subroutine calls to grip galore typical cases. But the FSCALE microcode illustrates galore absorbing parts of the 8087, specified arsenic the shifter, the adder, and the exponent converter, and besides reveals a hidden characteristic of the 8087, truthful hopefully you will find it interesting.

To research the microcode, I opened up an 8087 spot and created a high-resolution image pinch a microscope. The ample microcode ROM is successful the center, holding the 1648 micro-instructions that power the chip. The microcode motor connected the near steps done the microcode, handling jumps and subroutine calls. The bottommost half of the spot is the "datapath", the circuitry that performs floating-point calculations; it is divided into a 16-bit datapath for the number's exponent and a 64-bit datapath for the number's significand (also known arsenic the fractional part).

Die of the Intel 8087 floating-point portion chip, pinch main functional blocks labeled. The dice is 5mm×6mm.  Click for a larger image.

Die of the Intel 8087 floating-point portion chip, pinch main functional blocks labeled. The dice is 5mm×6mm. Click for a larger image.

Zooming successful connected the bottommost portion of the spot shows the datapath circuitry; I've highlighted the applicable parts below.1 The exponent ROM holds various constants. The exponent converter is simply a specialized circuit that examines exponents, detects typical values, and converts betwixt exponent formats.2 The shifter is simply a ample component; it allows a 64-bit3 worth to beryllium shifted near aliases correct by arbitrary amounts. (I wrote astir the 8087's shifter circuitry here.) The adder is the bosom of the 8087's calculations; it is utilized successful a loop for multiplication, division, and quadrate roots. The B registry holds 1 input to the adder, while aggregate sources tin supply the different input. The sum registry holds the adder's output. The 8 stack registers and the impermanent registers clasp floating-point numbers.

A close-up of the 8087's datapath, showing functional blocks that are utilized by FSCALE.

A close-up of the 8087's datapath, showing functional blocks that are utilized by FSCALE.

Details of the 8087

In this section, I'll explicate immoderate features of the 8087 that are important for the FSCALE microcode. To usage the 8087, a programmer stores values successful its 8 soul registers, organized arsenic a stack. Each registry holds an 80-bit floating-point number. To optimize performance, each worth successful the registry stack has an associated "tag" value, which is mostly invisible to the programmer.4 A tag labels a worth arsenic valid, special, zero, aliases empty. A "normal" floating-point worth is tagged arsenic valid. If the floating-point worth is infinity, Not a Number (NaN), aliases a denormalized value, then it is tagged arsenic special. A zero worth is tagged arsenic zero. Finally, if a registry is quiet (e.g., its worth has been popped disconnected the stack), the registry is tagged arsenic empty.

The 8087 besides has impermanent registers that it uses internally: tmpA, tmpB, and tmpC. Like the stack registers, tmpA and tmpB are 80-bit registers, along with 2 tag bits. However, tmpC only holds a 64-bit significand.

The 8087 supports a assortment of information types: floating-point numbers of various sizes, integers, and binary-coded decimal. But internally, everything is stored arsenic an 80-bit floating-point number called a "temporary real"; for the remainder of this article, I'll only beryllium considering impermanent existent values. A number has 3 parts: the motion bit, the 15-bit exponent, and the 64-bit significand (the fractional part), In astir cases, a floating-point number is represented by sign × significand × 2exponent. The significand is simply a 64-bit binary number of the shape 1.bbb...: a starring 1, followed by the binary constituent (the binary balanced of the decimal point) and the remainder of the bits.5 What makes floating-point numbers useful is that their scope covers the incredibly mini to the astronomically large, acknowledgment to the exponent, which ranges from -16382 to 16383. One important item is that the exponent is stored pinch a "bias" of 16383 added to it. Thus, the stored exponent is ever positive, moreover if the real exponent is negative.6

The 80-bit impermanent existent format. The triangle indicates the binary point, analogous to the decimal point. From the Intel Numerics Supplement.

The 80-bit impermanent existent format. The triangle indicates the binary point, analogous to the decimal point. From the Intel Numerics Supplement.

The 8087 supports respective types of numbers that are represented arsenic typical cases pinch typical exponents, arsenic shown below. Zero and infinity person some affirmative and antagonistic values. "Not a Number" (NaN) represents values that don't make sense, specified arsenic 0/0 aliases sqrt(-1); NaN has a ample number of representations, not a azygous value. The 8087 besides supports denormalized and unnormalized values, which are highly mini values wherever the significand doesn't person a starring 1.

The encoding of typical values. Based connected Table S-31 successful the Intel Numerics Supplement, but highly simplified. The "x" bits are arbitrary, arsenic agelong arsenic they don't conflict pinch different type.

The encoding of typical values. Based connected Table S-31 successful the Intel Numerics Supplement, but highly simplified. The "x" bits are arbitrary, arsenic agelong arsenic they don't conflict pinch different type.

The 8087 has a analyzable objection strategy pinch six types of exceptions to bespeak if thing went incorrect pinch an arithmetic operation. The astir superior is the "invalid operation", indicating that the cognition does not make sense, specified arsenic 0/0 aliases ∞-∞. It besides includes accesses to an quiet registry (stack overflow aliases underflow) aliases operations connected a NaN value. The 8087 besides has an overflow objection if a worth is excessively ample to store, an underflow objection if a worth is excessively small, and a divide-by-zero exception (excluding 0/0). A denormalized operand objection indicates that the consequence is excessively mini to shop arsenic a normal value, but tin beryllium stored arsenic a denormalized value. Finally, a precision objection indicates that a worth cannot beryllium represented precisely and must beryllium rounded. (Precision exceptions are very common; even 1/10 will output one.)

The 8087 provides fine-grain power complete each objection type, specified by bits successful the power register. If an objection is unmasked, the 8087 sends an interrupt to the 8086 processor, which handles the problem successful software, for lawsuit by terminating the programme aliases logging an error. Alternatively, the objection tin beryllium masked and the 8087 will proceed execution arsenic champion it can. For instance, an invalid consequence will beryllium replaced by NaN, while an overflow or divide-by-zero will beryllium replaced by infinity. A precision objection will consequence successful rounding. The constituent of masked exceptions is that calculations continue, yielding an reply that is arsenic meticulous arsenic possible; successful astir cases, this is what the programmer wants.

These features make the 8087 elastic and supply accuracy, but they besides make the microcode overmuch much complicated, since the combinations of typical cases request to beryllium handled appropriately.

The 8087's microcode

Executing an 8087 instruction tin require hundreds of soul steps to compute the result. These steps are implemented successful microcode pinch micro-instructions that specify each measurement of the algorithm. (Keep successful mind the 2 levels of instructions: the assembly connection instructions utilized by a programmer and the undocumented low-level micro-instructions wrong the chip.) The microcode ROM holds the 1648 micro-instructions that instrumentality the 8087's instruction set. I'm moving pinch the Opcode Collective to reverse-engineer the micro-instructions and afloat understand the microcode (link).

The 8087's micro-instructions are complicated, pinch galore area cases and advertisement hoc functions, but I'll supply a simplified overview. Each micro-instruction consists of 16 bits, arsenic shown below. The first 3 bits specify the micro-instruction's type, which controls the meaning of the remaining bits. The first type is simply a transportation operation, which transfers information from 1 soul registry to another. The 2 fields specify the root and destination. The 3 remaining bits are utilized for various typical cases. Next is simply a displacement operation, which uses the tube shifter to displacement a worth near aliases right. The 3rd type of micro-instruction controls the adder (which tin besides subtract). The various instructions see stack pointer operations, tag modification, exceptions, and subroutine return. The acold jump and acold telephone micro-instructions execute a jump aliases subroutine telephone to a target micro-address successful a fixed list. The information section allows conditional jumps/calls/returns based connected galore conditions, while the past spot inverts the condition. A section jump is simply a comparative jump to a adjacent micro-instruction.

Structure of an 8087 micro-instruction.

Structure of an 8087 micro-instruction.

The FSCALE microcode

When the 8087 starts executing an instruction, the instruction decoder circuitry determines the starting reside of the microcode corresponding to the instruction. This 11-bit reside is loaded into the microcode engine, which starts executing the microcode.7 The microcode for FSCALE (shown below) starts astatine decimal reside 748.8

The thought down FSCALE is straightforward: if you want to standard a floating-point number by 2N (for an integer N), you adhd N to the number's exponent. This allows you to multiply aliases disagreement by a powerfulness of 2 overmuch faster than utilizing the afloat floating-point multiplication operation. However, the microcode for FSCALE is unexpectedly analyzable and uses respective microcode subroutines. In brief, the microcode first checks for arguments that are zero and past handles different typical arguments. It converts the standard statement to an integer and adds it to the exponent. Finally, it handles immoderate overflow aliases underflow.

In much detail, the microcode regular starts by moving the first statement from the apical of the stack (st(0)) to the tmpA impermanent register. If the statement is zero, the regular instantly returns. (Thus, scaling 0 by anything—even NaN—will springiness a consequence of 0.) Next, the 2nd worth connected the stack (the 2nd argument) is moved to the tmpB impermanent register. Likewise, the codification returns if this worth is 0, truthful scaling thing by 0 leaves the worth unchanged.9 Next, a changeless worth is selected; selecting a changeless and utilizing it are 2 abstracted micro-instructions. (The 8087 has abstracted ROMs for 16-bit exponent constants and 67-bit significand constants; this 1 is an exponent constant.) In the normal case, execution jumps to reside #0763, skipping the telephone to subroutine SPECIAL_TMPS.

FSCALE: #0748 st(0) -> tmpA Input statement from apical of stack #0749 jmp #0776 if tmpA:tag ZERO Bail if 0 #0750 stackPtr++ #0751 st(0) -> tmpB Scale statement from stack(1) #0752 stackPtr-- #0753 jmp #0776 if tmpB:tag ZERO Bail if 0 #0754 expconst 0x403e Const 403e: exp displacement to person to int #0755 jmp #0763 if not tmp empty/special/div #0756 telephone SPECIAL_TMPS Special handling #0757 jmp #0762 if flag #0758 jmp #0761 if not tmpB:tag SPECIAL #0759 except:invalid Invalid exception, usage NaN #0760 NaN -> tmpA #0761 jmp #0776 if intr #0762 jmp #0775 if expConv[0] Return tmpA if expConv set, different continue #0763 tmpB:exp -> Breg Normal path #0764 tmpB:sign,exp -> expConv ExpConv will trial tmpB's sign #0765 expConst -> tmpC Const 403e #0766 adder: tmpC - Breg cin=1 403e-exp is magnitude to displacement to person tmpB to int #0767 sumreg:frac -> shiftcount Store successful shifter control #0768 displacement tmpB:frac R count byte spot Perform the shift #0769 displacement R -> Breg Breg holds standard statement arsenic an int #0770 jmp #0777 if neg Negative Breg needs abstracted handling #0771 adder: tmpA:exp + Breg cin=0 Add the standard to the exponent #0772 sumreg:frac -> expConv Put consequence successful expConv to cheque #0773 sumreg:frac -> tmpA:exp Update exponent pinch sum #0774 telephone NONNORMAL_RESULT if not exp normal Handle overflow/underflow #0775 tmpA -> st(0) Save consequence backmost to stack #0776 RNI Done: Run Next Instruction #0777 adder: tmpA:exp - Breg cin=1 Subtract Breg #0778 jmp #0772 Continue processing

Continuing astatine #0763, the 2nd statement is converted from a float to an integer, which takes a fewer steps. For example, suppose the statement is 9, which successful floating constituent is 1.001×23. The significand bits 1000 are "left justified", but for an integer, these bits request to beryllium "right justified" by shifting them to the right. In general, if the exponent is n, the significand is shifted correct by 63-n bits. But callback that the exponent is biased by 16383. Thus, the significand must beryllium shifted correct by 63-(exp-16383) bits, that is 0x403e-exp bits. (This explains the changeless 0x403e earlier successful the microcode.)

Converting a float to an int by shifting.

Converting a float to an int by shifting.

In the microcode, the subtraction takes respective steps. At #0763, the exponent of the 2nd statement is moved to the B register, 1 of the inputs to the adder (completely different from tmpB).10 Next, the motion and exponent are moved to the exponent converter, a circuit that, among different things, tests for overflow. Next, the changeless 0x403e (selected backmost astatine #0754) is moved to the tmpC register. At #0766, the adder is activated, subtracting the exponent from the constant.11 The adder puts the consequence into the sum register, and this worth is copied to the displacement count register, which controls the shifter. This worth indicates really galore bits the 2nd statement must beryllium shifted to person it to an integer. At #0768, the shifter is activated to displacement by the desired amount, utilizing some the spot displacement portion and the byte displacement part. As pinch the adder, activating the shifter and reference the consequence are abstracted micro-instructions; the consequence is put into the B register.

The halfway portion of the FSCALE instruction is yet performed astatine #0771, adding the 2nd statement to the first argument's exponent. The adder is activated to adhd the B registry worth (the scale) to the exponent, and the updated worth is stored successful tmpA's exponent. (Except if the standard facet is negative, it is subtracted via the #0777 path.)12 The worth is besides sent to the exponent converter circuit, which checks the exponent for overflow aliases underflow; if so, subroutine NONNORMAL_RESULT is called. But successful the normal case, the updated worth is copied from tmpA to the top-of-stack registry st(0). Finally, RNI (Run Next Instruction) indicates that the microcode regular is done and the instruction is completed. Thus, moreover successful the straightforward case, FSCALE takes astir 22 micro-instructions.

Handling quiet aliases typical arguments

What happens if an statement accesses an quiet stack location (i.e. stack underflow) aliases is simply a typical worth (infinity, denorm, NaN)? These cases are handled by a micro-subroutine that I'll telephone SPECIAL_TMPS15 because it processes typical values successful tmpA and/or tmpB. This subroutine is simply a general-purpose routine, utilized by basal arithmetic operations, FSCALE, FTST (test), and FPREM (partial remainder).

The power travel done SPECIAL_TMPS is alternatively convoluted since the codification must prioritize issues if, say, 1 statement is quiet and the different is simply a denorm. I'll conscionable springiness a little summary; spot the footnote13 for details. First, the subroutine converts immoderate denorms to unnorms. Then it checks for entree to quiet stack locations, raising an objection aliases interrupt if so. Then it checks the 2 arguments again. If either is NaN, an objection aliases interrupt is triggered. Otherwise, it returns a position indicating the type of arguments.

Unexpectedly, if both arguments are NaN, the codification compares the 2 NaN values and returns the larger. This behaviour whitethorn look very weird, but it's a documented feature.14 You mightiness deliberation that NaN is simply a azygous value, but it's really an tremendous family of values. The thought was that the programmer could usage different NaN values to awesome wherever a problem occurs. For instance, you could put a different NaN successful each location of an uninitialized array, truthful you could show which position was accessed. For immoderate reason, the designers of the 8087 decided that if you execute an cognition pinch 2 different NaNs, the consequence is the larger one. Thus, the microcode needs codification that detects if some operands are NaN and computes the larger, utilizing a subtraction for the comparison (#1518).

SPECIAL_TMPS (J5): #1484 telephone SPECIAL_VAL if tmpA:tag SPECIAL Handle typical values successful tmpA/tmpB #1485 xchg tmp #1486 telephone SPECIAL_VAL if tmpA:tag SPECIAL Handle tmpB special #1487 xchg tmp #1488 1 -> emblem Flag=1 by default #1489 jmp #1500 if not tmp empty/special/div 0 -> expConv if tmps okay #1490 1 -> expConv #1491 jmp #1497 if not tmpA/B empty #1492 except:invalid Invalid if either empty #1493 jmp #1525 if comparison instruction No NaN for comparison #1494 jmp #1511 if intr Return if interrupt not masked #1495 NaN -> tmpA NaN if interrupt masked #1496 return #1497 jmp #1502 if tmpA:tag SPECIAL Special cases #1498 jmp #1505 if tmpB:tag SPECIAL #1499 0 -> emblem Div normal path: #1500 zero -> expConv Return emblem 0, expConv 0 #1501 return #1502 telephone SPECIAL_VAL TmpA special #1503 jmp #1512 if not emblem Jump if NaN, fallthrough if infinity #1504 jmp #1509 if not tmpB:tag SPECIAL #1505 xchg tmp TmpB special #1506 telephone SPECIAL_VAL #1507 xchg tmp #1508 jmp #1521 if not emblem Jump if NaN, return if infinity #1509 0 -> emblem Clear flag, return #1510 return #1511 RNI End instruction pinch interrupt #1512 jmp #1522 if not tmpB:tag SPECIAL TmpA NaN, now cheque tmpB #1513 xchg tmp #1514 telephone SPECIAL_VAL Check tmpB #1515 xchg tmp #1516 jmp #1522 if emblem Jump if tmpB is not NaN #1517 except:invalid Invalid exception #1518 tmpB:frac -> Breg Both args are NaN, find larger #1519 adder: tmpA:frac - Breg cin=1 #1520 jmp #1522 if adder motion See if tmpA < tmpB #1521 tmpB -> tmpA Take larger #1522 except:invalid Invalid exception #1523 jmp #1525 if comparison instruction No interrupt for comparison instruction #1524 jmp #1511 if intr End instruction pinch interrupt #1525 1 -> emblem Return pinch emblem set #1526 return End of J5

This subroutine makes dense usage of a helper subroutine, SPECIAL_VAL,16 that processes 1 argument. The helper converts a denormalized statement to an unnormalized argument, raising an objection aliases interrupt arsenic appropriate. It besides flags an input of infinity.

The hardware for the micro-instruction that exchanges tmpA and tmpB astatine #1485 is interesting. Instead of physically moving the values betwixt the 2 registers, the micro-instruction toggles a flip-flop that exchanges the meaning of tmpA and tmpB. That is, if the flip-flop is set, a reference to tmpA goes to tmpB and vice versa. (This is simply a modular instrumentality successful microprocessors; the Intel 8080's XCHG instruction exchanges the DE and HL registers successful a akin way. The Z80 uses the aforesaid instrumentality for the EX and EXX instructions to speech the regular registry group pinch the secondary registry set.)

The Intel 8087 spot is packaged successful a 40-pin DIP (dual in-line package), arsenic are the 8080 and Z80. This photograph is present arsenic a break from each the microcode.

The Intel 8087 spot is packaged successful a 40-pin DIP (dual in-line package), arsenic are the 8080 and Z80. This photograph is present arsenic a break from each the microcode.

Handling a non-normal result

If you return a very ample number and standard it larger, you tin extremity up pinch overflow. If you return a very mini number and standard it smaller, you tin extremity up pinch a denormalized number aliases underflow. This will trigger an overflow, denorm, aliases underflow excaption, and an interrupt if unmasked. Moreover, the 8087 supports 4 rounding modes: round to nearest valid value, information down (toward -∞), information up (toward +∞), aliases information (chop) toward zero. Depending connected the rounding mode, an overflow tin consequence successful either ∞ aliases the largest imaginable floating-point number. Similarly, an underflow tin consequence successful either zero aliases the smallest imaginable floating-point number. And depending connected the infinity mode (affine aliases projective), infinity tin beryllium either signed aliases unsigned. Thus, the FSCALE microcode needs to grip galore typical cases for the result.

The subroutine to grip a non-normal consequence successful tmpA is below. One absorbing micro-instruction is update overflow/underflow exceptions, which triggers an objection if appropriate. For astir exceptions, a micro-instruction triggers the objection (for example, except:precision astatine #0346). But for the overflow and underflow exceptions, the microcode delegates the task to hardware. Specifically, the 8087's "exponent converter" circuit examines the exponent to spot if an overflow or underflow exists, based connected the selected floating-point precision. The micro-instruction sets the overflow and underflow flags based connected these values. Thus, a analyzable task is performed by a azygous microcode instruction, acknowledgment to the hardware support of the exponent converter.

NONNORMAL_RESULT (J16): #0318 return if tmpA:tag ZERO Handle non-normal result #0319 update overflow/underflow exceptions Trigger exceptions if exp conv says to #0320 expconst 0x6000 The interrupt bias changeless 0x6000 #0321 jmp #0329 if not intr #0322 expConst -> Breg Interrupt path #0323 jmp #0326 if neg #0324 adder: tmpA:exp + Breg cin=0 Add bias for underflow #0325 jmp #0327 #0326 adder: tmpA:exp - Breg cin=1 Subtract for bias overflow #0327 sumreg:frac -> tmpA:exp New exponent to tmpA #0328 return Interrupt, truthful done #0329 jmp #0344 if neg Masked exception #0330 tmpA:exp -> Breg Underflow #0331 adder: 1 - Breg cin=1 Amount to displacement denormal #0332 telephone CREATE_DENORM Create a denormal #0333 adder: zero + Breg cin=0, roundmode Add zero to round #0334 telephone ADJUST_PRECISION Adjust to specified precision #0335 jmp #0340 if Sum registry is zero If zero, return +/- zero arsenic appropriate #0336 zero -> tmpA:exp Denorm: exponent is 0 #0337 sumreg:frac -> tmpA:frac Save denorm fraction #0338 typical -> tmpA tag Tag denom arsenic special #0339 return #0340 tmpA motion -> motion latch Return +/- zero #0341 zero -> tmpA #0342 motion latch -> tmpA sign #0343 return #0344 NaN/Inf -> tmpA:exp Overflow: possibly return infinity #0345 tmpA:frac -> tmpB:frac Save tmpA frac successful tmpB #0346 except:precision Set precision exception #0347 Inf -> tmpA:frac Put infinity successful frac #0348 typical -> tmpA tag Mark infinity arsenic special #0349 return if not information chop If rounding up, return infinity #0350 1 -> Breg Return max float: set down #0351 adder: tmpA:exp - Breg cin=1 #0352 sumreg:frac -> tmpA:exp Exp=7fff-1=7ffe #0353 adder: zero - Breg cin=1 #0354 sumreg:frac -> tmpA:frac Frac 0-1 = ff...ff #0355 norm -> tmpA tag Normal value #0356 return if tmpB:frac[63] Return max float unless unnorm #0357 tmpB:frac -> tmpA:frac Return original tmpA frac #0358 return

The 8087 has absorbing behaviour if an overflow aliases underflow is unmasked and an interrupt occurs. The thought is to fto the interrupt handler cognize what the exponent should person been. However, the due worth can't beryllium utilized since it is excessively large aliases excessively mini to fresh successful the exponent section (which is why the objection occurred). The solution is to adhd aliases subtract the changeless 0x6000, resulting successful an exponent that fits. The interrupt handler tin subtract aliases adhd this changeless to get the correct exponent. Lines #0322 to 0328 execute this summation aliases subtraction.

For a masked underflow, a denorm worth is created by the subroutine CREATE_DENORM. The worth is rounded to the specified precision by ADJUST_PRECISION. Finally, if the worth is excessively mini for a denorm, the worth +0 aliases -0 is returned arsenic appropriate.

For a masked overflow, the 8087 either returns Infinity aliases the largest-possible float, depending connected the specified rounding mode. Infinity is represented by an exponent of each 1s, and a significand of 1000...; these values are loaded straight onto the autobus by transistors. The maximum float, however, is computed: 1 is subtracted from the infinity exponent, and 1 is subtracted from a zero significand.

Helper subroutine: creating a denormal

One controversial characteristic of the 8087 is denormals, numbers that are smaller than "regular" floats. Recall that floating-point numbers person a significand pinch the first spot group to 1. But what happens if you deed the smallest imaginable exponent and want an moreover smaller number? The 8087 lets you break the norm that the significand starts pinch 1, producing smaller numbers known arsenic denormalized numbers aliases denorms. Denorms importantly widen the range, providing numbers up to a facet of 263 smaller. However, denorms don't person arsenic overmuch precision since the precocious bits are "wasted". Moreover, calculations pinch denorms tin beryllium substantially slower because special handling is required.

Example of a normal number, reduced by a facet of 8, resulting successful a denormal.

Example of a normal number, reduced by a facet of 8, resulting successful a denormal.

The sketch supra shows a normal number pinch the minimum imaginable exponent (-16382, which is 1 aft biasing). Dividing the number by 8 (or scaling by -3) creates a denorm since the exponent can't beryllium reduced immoderate further. Instead, the significand is shifted 3 bits to the right. The exponent is replaced pinch the typical worth 0, indicating that the number is simply a denorm.

In the 8087, denorms are created by a microcode subroutine that I'll telephone CREATE_DENORM; it is utilized by galore arithmetic operations, not conscionable FSCALE. This subroutine takes a normal number and a displacement amount. By shifting the normal number (as successful the illustration above), it creates a denormalized number. The microcode (below) uses the exponent converter to cheque if the displacement is 64 aliases more. If so, location will beryllium thing near aft the shift, truthful zero is returned. Otherwise, the worth is shifted to the correct and the denorm is stored successful the B register.

CREATE_DENORM (J20): #0522 sumreg:frac -> expConv Create denorm #0523 sumreg:frac -> shiftcount Number of bits to shift #0524 jmp #0528 if exponent[6:14] == 0 Jump if < 64 #0525 zero -> Breg No bits left, usage zero #0526 displacement tmpA:frac L 0 bytes, 0 bits Run done shifter? #0527 jmp #0532 #0528 displacement tmpA:frac R count byte spot Shift correct by the specified amount #0529 displacement R -> Breg Result to Breg #0530 displacement tmpA:frac L ~count byte spot Now displacement backmost for sticky test #0531 NOP Wait for shifter #0532 rounding(h) -> Breg[grs] Store the 3 rounding bits successful the Breg #0533 return

But why is the worth past shifted to the near (#0530)? The intent of this is to get the rounding bits. One of the principles of the 8087 is to get rounding correct, which is simply a batch harder than it seems. In bid to determine really to information up a number, you request to support way of an impossibly ample number of bits. For instance, if you cipher 1 + 0 and information up, you get 1. But if you calculate, say, 1 + 2-10000 and information up, you get a float a spot higher than 1. The problem is really do you separate the 2 sums earlier rounding, without storing thousands of bits?

The instrumentality is that the 8087 keeps 3 bits for usage successful rounding: the "guard" bit, the "round" bit, and the "sticky" bit. If you see a "tail" of bits to the correct of the significand, the defender spot is the astir important spot of the tail, followed by the information bit. The sticky spot is special: it is the OR of each the remaining bits successful the tail, indicating if any of them are 1. Thus, 1 + 2-10000 has the sticky spot set, while 1 + 0 does not, truthful the 2 values tin beryllium rounded up differently. To make the sticky bit, the 8087 uses a very ample 64-bit NOR gross that tests the tail bits successful parallel.

A sketch showing really the guard, round, and sticky bits are computed from a correct shift. The numbers successful this illustration are different from the erstwhile example.

A sketch showing really the guard, round, and sticky bits are computed from a correct shift. The numbers successful this illustration are different from the erstwhile example.

When a number is shifted to the correct (e.g., erstwhile creating a denormal), bits are mislaid disconnected the right. To make the rounding bits, the worth is shifted to the left, keeping each the tail bits that will yet beryllium discarded, and discarding the bits that will beryllium successful the last significand. The apical 2 bits spell into the defender and information bits, while the remaining bits are ORed together to make the sticky spot from the rest.17 The sketch supra is an illustration of this process. Suppose the worth is being shifted to the correct by 4 bits. The tail bits abcd (or astatine slightest d) will get mislaid successful the shift. The rounding bits are computed by shifting the original significand to the correct by 59 bits (the complement of 4). Bit 62 (a) becomes the caller defender bit, bit 61 (b) becomes the caller information bit, and the OR of the remaining 64 bits becomes the caller sticky bit. (Note that the aged guard, round, and sticky bits get ORed successful too, truthful they aren't lost.) Merging the significand from the first displacement pinch the rounding bits from the 2nd displacement produces the desired result.

Helper subroutine: adjusting precision

Although the 8087 supports 3 lengths of floats, it performs each calculations pinch 80-bit "temporary reals". At the extremity of an instruction, it converts the consequence to the desired length. (As a consequence, astir instructions aren't immoderate faster if you usage a shorter float.) A microcode subroutine, which I telephone ADJUST_PRECISION, converts the consequence to the precision that is specified successful the 8087's power word, using the specified rounding mode. This subroutine is utilized by astir of the arithmetic instructions.

The 8087 supports 3 types of existent numbers. From the Intel Numerics Supplement.

The first codification way handles impermanent reals (which person 64 bits of precision). The power connection specifies 1 of 4 rounding modes. However, location are only 2 actions that tin beryllium taken for a peculiar significand: either information down (chop) aliases information up (chop and increment by 1). This determination is made by analyzable logic circuits that analyse the rounding bits, the rounding mode, and the motion to find whether to information up aliases down. This simplifies the microcode but makes the hardware much complicated. The microcode performs a conditional return, returning if the significand doesn't request to beryllium rounded up. Otherwise, the microcode increments the significand by adding 0 pinch a carry-in. It past checks for overflow, successful which lawsuit it replaces the worth pinch Infinity and sets a typical flag.18

ADJUST_PRECISION (J11): #0299 jmp #0306 if not precision64 #0300 return if not information up, update CC1 Update information code, possibly return #0301 adder: sumreg:frac + 0 cin=1 Add 1 to information up #0302 return if not sumreg[64] #0303 Inf -> sumreg:frac,sign Return infinity if overflow #0304 2count++ Set typical flag #0305 return #0306 23/52 -> shiftcount Short aliases agelong real: get due shift #0307 displacement sumreg:frac,rnd L count byte spot sticky Shift to make rounding bits #0308 NOP Wait for shifter to complete #0309 rounding(H) -> sumreg[grs] Store rounding bits #0310 displacement sumreg:frac R ~count byte spot Shift correct to driblet excess bits #0311 displacement R -> sumreg:frac #0312 jmp #0314 if not information up, update CC1 Update information code #0313 adder: sumreg:frac + 0 cin=1 Round up if appropriate #0314 displacement sumreg:frac L ~count byte spot Shift near to realign #0315 displacement L -> sumreg:frac,sign #0316 return if not sumreg[64] Return if not overflow #0317 jmp #0303 Return infinity

The codification is much analyzable when returning a smaller precision (short existent aliases agelong real), since the significand must beryllium shortened. First, the codification astatine #0306 loads the shifter pinch either 23 aliases 52, depending connected the precision specified successful the power word, and past shifts the value left. This produces the rounding bits arsenic successful the erstwhile section. Next, the worth is shifted to the right, shortening it to the desired length. As before, the significand is incremented aliases not, depending connected whether it should beryllium rounded up aliases not. Finally, the worth is shifted backmost to the left, truthful the astir important spot of the significand is connected the left. As before, if rounding up caused an overflow, infinity is returned.

One bizarre characteristic is that a jump pinch the "round up" conditional besides has a broadside effect of updating the 8087's programmer-visible information codification registry (CC1), indicating if the consequence was rounded up aliases down. That is, the 8087 has other circuitry to observe this circumstantial information and load the worth into the information codification latch. Strangely, the 8087 archiving doesn't picture this information codification action; Intel didn't archive it until the 387SX floating-point spot successful 1987.19

Conclusions

Floating-point has a agelong history earlier the 8087. For instance, the IBM System/360 mainframes (1964) supported 32-bit and 64-bit floating-point numbers. In 1977, AMD introduced the Am9511 floating-point chip, supporting 16- and 32-bit floating-point numbers, on pinch transcendental functions. What made the 8087 revolutionary is that it was cautiously designed to beryllium arsenic mathematically meticulous arsenic possible, mostly acknowledgment to numerical master William Kahan. (The 8087 led to the IEEE 754 Standard, now utilized by almost every computer and ending the anarchy of incompatible floating-point standards.)

The 8087 ended up extraordinarily analyzable pinch 3 different sizes of floating-point numbers, 4 sizes of integers, 4 rounding modes, infinity modes, a postulation of exceptions that could beryllium masked aliases unmasked, denormalized and unnormalized numbers, signed and unsigned infinities, signed zeros, and a full family of Not-a-Numbers. These features combine, yielding galore area cases. The 8087 deals pinch this complexity some done specialized circuits and done tangled microcode.

How analyzable is the 8087? For users who didn't person an 8087 chip, Intel sold an 8087 Support Library that precisely emulated the 8087's instructions (but overmuch slower). The emulator took 16K bytes of 8086 code, which was a batch erstwhile a afloat BASIC expert could fresh successful 8K. Another measurement of looking astatine this is that the hardware of the 8087 drastically reduced the magnitude of package required: the 8087 itself utilized 3.3K of microcode, compared to the 16K for the emulator successful 8086 code.

I scheme to proceed reverse-engineering the 8087 microcode; for updates, travel maine on Bluesky (@righto.com), Mastodon (@[email protected]), or RSS. I've been moving connected this pinch the members of the "Opcode Collective", particularly Smartest Blob and Gloriouscow, who converted the ROM images to microcode information and extensively analyzed the contents. See the 8087 repository connected GitHub for more.

Notes and references

More