15. The FloatingPoint Coprocessor
Part of
the Hawk Manual

15.1. FloatingPoint Registers
15.1.1. Coprocessor Status Register — 0
15.1.2. FloatingPointLow Register — 1
15.1.3. FloatingPoint Accumulators — 2 and 3
15.2. COGET Operations
15.2.1. Negate — op = 010_{2}
15.2.2. Absolute Value — op = 011_{2}
15.3. COSET Operations
15.3.1. Convert Integer to Floating — op = 010_{2}
15.3.2. Square Root — op = 011_{2}
15.3.3. Add — op = 100_{2}
15.3.4. Subtract — op = 101_{2}
15.3.5. Multiply — op = 110_{2}
15.3.6. Divide — op = 111_{2}
15.4. Examples
The floatingpoint coprocessor shares the COSTAT register with other coprocessors (see Section 1.3.3), and it has 3 other interface registers that can also be referenced using COGET and COSET (see Section 11.4). to allow direct access to two 64bit floatingpoint accumulators.
The following constants should be defined by the assembly header file float.h to support access to these registers:
;COSTAT = 0 ; defined in hawk.h FPLOW =: 1 ; the low half of long floating operands FPA0 =: 2 ; floatingpoint accumulator 0 FPA1 =: 3 ; floatingpoint accumulator 1
31  30  29  28  27  26  25  24  23  22  21  20  19  18  17  16  15  14  13  12  11  10  09  08  07  06  05  04  03  02  01  00 
unused  sl  u  0 0 1  unused  en  u 
From the point of view of floatingpoint coprocessor only inspects a subset of the bits in COSTAT (see Section 1.3.3). The floatingpoint coprocessor is coprocessor number 1. Thus it is enabled when COSTAT:1 is set in the enable field (enable), and it is selected when COSTAT:10:8 (select) is set to 001.
COSTAT:12 (sl) is part of the coprocessor operation field (coop). If this is reset, the coprocessor uses a short (32bit) floatingpoint representation. If it is set, it uses the long (64bit) representation. The other bits in coop are unused, although they may be used in enhanced floatingpoint coprocessors.
The following constants should be defined by the assembly header file float.h to support use of the floatingpoint coprocessor; these can be added together (or ored together) to create a value to be placed in COSTAT:
FPENAB =: #0002 ; enable the floatingpoint unit FPSEL =: #0100 ; select the floatingpoint unit FPLONG =: #1000 ; operate in long format FPSHORT =: #0000 ; operate in short format (default) FPENBIT =: 1 ; bit number of FPENAB
Strictly speaking, there is no reason to explicitly specify short format, since it is the default, but an explicit statement of short mode makes programs easier to read.
To enable and select the floatingpoint coprocessor for long floating operations while turning off any other coprocessors in the system, use the following instruction sequence:
LIL R1,FPENAB+FPSEL+FPLONG COSET R1,COSTAT
For applications that use multiple coprocessors concurrently, to select the already enabled floatingpoint coprocessor and set it in short mode, use the following instruction sequence:
COGET R1,COSTAT TRUNC R1,8 ADDI R1,R1,FPSEL+FPSHORT COSET R1,COSTAT
To test if the floatingpoint coprocessor is currently enabled, use
COGET R1,COSTAT BITTST R1,FPENBIT BBR NOFP
31  30  29  28  27  26  25  24  23  22  21  20  19  18  17  16  15  14  13  12  11  10  09  08  07  06  05  04  03  02  01  00 
mantissa:31:0 
The floatingpoint low (FPLOW) register holds the least significant 32bits of a long floatingpoint operand. These are the low 32 bits of the mantissa. The contents of FPLOW are not defined when operating in 32bit mode.
In 64bit mode, FPLOW must be set before using COSET to set or operate on the high half of a floatingpoint accumulator, and it is automatically set as a side effect of using COGET to read the low half of a floatingpoint accumulator. COGET Rd,FPLOW has an undefined effect on the condition codes.
The floatingpoint coprocessor contains two 64bit floatingpoint accumulators, fpa[0] and fpa[1]. The apparent format of these registers appears different in short floatingpoint mode, where they appear to be only 32 bits. In fact, the hardware always retains an 11 bit exponent and a 52bit mantissa, at minimum, with the extra bits suppressed in short mode. IEEE standard floatingpoint format is supported. This means that the 53bit mantissa is usually 54 bits, with a hidden onebit (bit 52) except in the case of unnormalized numbers, where this hidden bit is zero, signified by a zero exponent.
31  30  29  28  27  26  25  24  23  22  21  20  19  18  17  16  15  14  13  12  11  10  09  08  07  06  05  04  03  02  01  00 
s  exp:7:0  mantissa:51:29 
In short mode, the floatingpoint operands are 32 bits, and the floating low register is not used. IEEE floatingpoint format is used. In short format, the 11bit exponent is shortened to just 8 bits, and only the most significant 24 bits of the mantissa (including the hidden bit defined by the IEEE format) are used.
31  30  29  28  27  26  25  24  23  22  21  20  19  18  17  16  15  14  13  12  11  10  09  08  07  06  05  04  03  02  01  00  
s  exp:10:0  mantissa:51:32 
In long mode, the floatingpoint operand registers are 64 bits, and the floating low bits are used to access the least significant 32 bits of the operand. IEEE floatingpoint format is used. In long format, the full 11bit exponent is used, and the most significant 21 bits of the mantissa (including the hidden bit defined by the IEEE format) are given here, while the least significant 32 bits are found in FPLOW. To load fpa[1] from with a 64bit floatingpoint number stored in r[3] and r[4], least significant half first, use:
COSET R3,FPLOW COSET R4,FPA1 ; uses FPLOW
The corresponding code to get a 64bit number from fpa[1] into r[3] and r[4] is:
COGET R4,FPA1 ; sets FPLOW COGET R3,FPLOW
Conversion between short and long format may be done by changing the value of the coprocessor operation field in COSTAT between the time a value is put into a floatingpoint operand register and the time it is retrieved.
COGET  N = r[dst]:31  — result is negative  
Z = (r[dst] = 0)  — result is zero  
V = 0  
C = (exp = 11111111111_{2}) — not a number 
In both long and short format, COGET sets the condition codes after reading fpa[0] or fpa[1] to report on the entire floatingpoint number, including that part (if any) loaded into FPLOW. N and Z indicate a negative or zero value, while C is used to report values that are NANs (not a number) or infinity. The overflow conditiion code, V, is always reset. As a result, the signed comparison instructions such as BGT and BLE will correctly report the relationship of the operand to zero.
Several simple floatingpoint operations are initiated by getting coprocessor register numbers 4 through 15. That is, the coprocessor register number is used partially to select one of the floatingpoint accumulators and partly as a coprocessor operation code.
07  06  05  04  03  02  01  00  15  14  13  12  11  10  09  08  
0 0 0 1  dst (0)  0 0 1 1  op  fr  COGET  dst,src  r[dstx] = op(co[fr]) 
To conform with the register numbers described in Section 15.1, op=000_{2} gives access to COSTAT and FPLO, and op=001_{2} gives access to fpa[fr] (that is, fpa[0] and fpa[1]). This leaves a total of 6 additional operations that can be initiated by COGET to operate on the selected floating point accumulator.
The following operations done by COGET are defined. None of these change the contents of the floatingpoint accumulators. These instructions may stall the CPU, preventing it from executing additional instructions if the floatingpoint unit is busy with a previously initiated computation. Because the floatingpoint accumulators have register numbers 2 and 3, the definitions provided in float.h are off by two from the actual values of the floatingpoint op field. This allows these values to be added to the accumulator numbers to form the operation code:
FPNEG =: 2 ; op = 010  negate FPABS =: 4 ; op = 011  absolutevalue
COGET dst,FPAx+FPNEG retrieves the negated floatingpoint value of a floatingpoint accumulator, setting the condition codes to report on the result (see Section 1.3.3). In long mode, FPLOW is also set to hold the low bits of the result.
For example, in long mode, the value in fpa[0] may negated and loaded into the double register r[3]r[4] as follows:
COGET R4,FPA1+FPNEG ; high half COGET R3,FPLOW ; low half
The value in fpa[0] can be negated and moved it to fpa[1] via r[1], in either short or long mode, as follows:
COGET R1,FPA0+FPNEG COSET R1,FPA1
To negate the short floatingpoint value in r[3] without using the floatingpoint unit, the following instruction sequence will work:
CMP R1,R1 ; this always sets C ADJUST R3,CMSB ; toggle the sign bit
COGET dst,FPAx+FPABS retrieves the absolute value of a floatingpoint accumulator, setting the condition codes to report on the result (see Section 1.3.3). In long mode, FPLOW is also set to hold the low bits of the result.
For example, in long mode, to divide fpa[0] by the absolute value of fpa[1] this instruction sequence will work:
COGET R3,FPA1+FPABS COSET R3,FPDIV+FPA0
To take the absolute value of a long floatingpoint value in R3R4 without using the floatingpoint unit, the following instruction sequence will work:
SL R4,1 SRU R4,1 ; clear the sign bit
Time consuming floatingpoint operations are initiated by setting coprocessor register numbers 4 through 15. That is, the coprocessor register number is used partially to select one of the floatingpoint accumulators and partly as a coprocessor operation code.
07  06  05  04  03  02  01  00  15  14  13  12  11  10  09  08  
0 0 0 1  srcx (0)  0 0 1 0  op  fr  COSET  dst,src  op(co[fr], r[srcx]) 
To conform with the register numbers described in Section 15.1, op=000_{2} gives access to COSTAT and FPLO, and op=001_{2} gives access to fpa[fr] (that is, fpa[0] and fpa[1]). This leaves a total of 6 additional operations that can be initiated by COGET to operate on the selected floating point accumulator.
The following operations are done by COGET. All of these change the contents of the floatingpoint accumulators, they may stall the CPU if the floatingpoint unit is already busy, and they may stall later COGET instructions until the operation they initiate is complete. Because the floatingpoint accumulators have register numbers 2 and 3, the definitions provided in float.h are off by two from the actual values of the floatingpoint op field. This allows these values to be added to the accumulator numbers to form the operation code:
FPINT =: 2 ; op = 010  convert integer to floating FPSQRT =: 4 ; op = 011  square root FPADD =: 6 ; op = 100  add FPSUB =: 8 ; op = 101  subtract FPMUL =: 10 ; op = 110  multiply FPDIV =: 12 ; op = 111  divide
COSET src,FPINT+FPAx converts the integer operand to a normalized floatingpoint value in fpa[fr]. In long mode, FPLOW must already hold the low 32 bits of the integer operand.
For example, if r[3] and r[4] contain a 64bit signed integer, least significant word first, and if the floatingpoint unit is in long mode, the integer may be converted to floatingpoint in fpa[1] as follows:
COSET R3,FPLOW COSET R4,FPINT+FPA1
If r[3] contains a 32bit unsigned integer, and if the floatingpoint unit is in long mode, the integer may be converted to floatingpoint in fpa[1] as follows:
COSET R3,FPLOW COSET R0,FPINT+FPA1
If register r[3] contains a 32bit signed integer, and if the floatingpoint unit is in short mode, the integer may be converted to floatingpoint in fpa[1] as follows:
COSET R3,FPINT+FPA1
Floating to integer conversion, truncated or rounded, must be done by
software.
COSET src,FPSQRT+FPAx takes the square root of the operand and puts it in fpa[fr] In long mode, FPLOW must already hold the low 32 bits of the long floating operand.
For example, if r[3] contains a short floatingpoint number, and if the floatingpoint unit is in short mode, the square root may be taken in fpa[1] as follows:
COSET R3,FPSQRT+FPA1
COSET src,FPADD+FPAx adds the floatingpoint operand to fpa[fr]. In long mode, FPLOW must already hold the low 32 bits of the long floating operand.
For example, if r[3] and r[4] contain a long floatingpoint number (least significant word first), and if the floatingpoint unit is in long mode, the number may be added to fpa[1] as follows:
COSET R3,FPLOW COSET R4,FPADD+FPA1
COSET src,FPSUB+FPAx subtracts the floatingpoint operand from fpa[fr]. In long mode, FPLOW must already hold the low 32 bits of the long floating operand.
For example, if r[3] contains a short floatingpoint number, and if the floatingpoint unit is in short mode, the number may be subtracted from fpa[1] as follows:
COSET R3,FPSUB+FPA1
Note that, unlike integer arithmetic, the subtract operation does not set the condition codes. To compare the two short floatingpoint numbers in r[3] and r[4], use this sequence:
COSET R3,FPA1 COSET R4,FPSUB+FPA1 COGET R0,FPA1 ; sets the condition codes
COSET src,FPMUL+FPAx multiplies fpa[fr] by the floatingpoint operand. In long mode, FPLOW must already hold the low 32 bits of the long floating operand.
For example, if r[3] and r[4] contain a long floatingpoint number (least significant word first), and if the floatingpoint unit is in long mode, fpa[1] may be multiplied by the number as follows:
COSET R3,FPLOW COSET R4,FPMUL+FPA1
COSET src,FPDIV+FPAx divides fpa[fr] by the floatingpoint operand. In long mode, FPLOW must already hold the low 32 bits of the long floating operand.
For example, if r[3] contains a short floatingpoint number, and if the floatingpoint unit is in short mode, fpa[1] may be divided by r[3] as follows:
COSET R3,FPDIV+FPA1
Consider computing y = ax^{2} + bx + c, where y, a, b, c and x are short floatingpoint numbers stored in r[3] to r[7], in that order. Also, assume that coprocessors are turned off when not in use. The following code works:
LIL R3,FPENAB+FPSEL+FPSHORT COSET R3,COSTAT ; turn on floating coprocessor COSET R4,FPA1 ; FPA1 = a COSET R7,FPMUL+FPA1 ; FPA1 = ax COSET R5,FPADD+FPA1 ; FPA1 = ax + b COSET R7,FPMUL+FPA1 ; FPA1 = (ax + b)x COSET R6,FPADD+FPA1 ; FPA1 = (ax + b)x + c COGET R3,FPA1 COSET R0,COSTAT ; turn off floating coprocessor
Subroutines that use the floatingpoint coprocessor can be written so that they restore the coprocessor status word to its former state on return, and so that they do not turn off any other coprocessors that may be in use while they turn on and select the floatingpoint coprocessor. The following code illustrates this, using a variable indexed off of R2 to hold the saved floatingpoint status, and using R3 and R4 as temporaries:
COGET R3,COSTAT STORE R3,R2,SVCOSTAT ; save old COSTAT TRUNC R3,8 ; clear coop and select fields LIL R4,FPENAB + FPSEL ... OR R3,R4 ; set enable, select and mode bits COSET R3,COSTAT ; update COSTAT ... code using floating point ... LOAD R3,R2,SVCOSTAT COSET R3,COSTAT ; restore COSTAT
Many interrupt and trap service routines make no use of the floatingpoint coprocessor and return to the same code that was interrupted or that caused the trap. For such trap service routines, there is no need to take any action with regard to the floatingpoint coprocessor. When, however, a trap or interrupt service routine needs to do floatingpoint computation or performs a context switch, the entire state of the floatingpoint coprocessor must be saved.
Saving and restoring the state of the coprocessor in an interrupt service routine requires care. If the coprocessor is not enabled, only the status needs saving. If enabled, it must be selected before accessing the registers to save them. Saving the registers in long mode is always safe. Note that FPLOW must always be saved because it need not represent the low half of either accumulator at the time of interrupt.
Here is code to save the entire coprocessor state into registers R8 through R13:
COGET R8,COSTAT ; COSTAT into R8 BITTST R8,FPENBIT BBR NOFPSV ; if floatingpoint coprocessor enabled EXTB R9,R8,R0 ; move just the enable bits ADDI R9,R9,FPSEL+FPLONG COSET R9,COSTAT ; select flotingpoint long mode COGET R9,FPLOW ; FPLOW into R9 COGET R11,FPA0 COGET R10,FPLOW ; FPA0 into R10 (low) and R11 (high) COGET R13,FPA1 COGET R12,FPLOW ; FPA1 into R12 (low) and R13 (high) NOFPSV: ; endif
The registers cannot be restored in the order they were saved because of COSET side effects and because the value in COSTAT during saving is not the same as the saved value of COSTAT.
In addition to defining values for fields of COSTAT, float.h should define the F directive to store a word in memory holding a floating point value, and the LIF instruction, equivalent to LIW (see Section 5.2). but with a floating point operand. The following examples should work:
F 3.1415 ; like W, but with a floating value F 3E2 ; equivalent to 300.0 F +1.0e2 ; equivalent to 0.01 LIF 123.45E+7 ; like LIW, but with a floating value
Decimal floatingpoint values have an optional sign on both mantissa and optional exponent, which comes after the letter E or e. The fractional part of the mantissa is also optional.