Working with MIPS Floating Point Architecture using PCSPIM
Until now we have checked only integer computations in PCSPIM from our last tutorials, but in reality computations also involve floating point arithmetic. For this purpose MIPS supports various floating point instructions along with having 32 special floating point registers $f0 − $f31. Some very interesting facts about Floating Point (hereafter called FP) organization and FP programming are given below. Understand these as they will help you in working with floating point numbers in the MIPS architecture.
- There are thirty two 32-bit registers for floating point operation. These registers are separate from the integer registers (R0 – R31) that you have worked with earlier.
- F0 is like any other register unlike R0 which is always zero. F0 can be used as a destination.
- You cannot mix FP registers and Integer registers in one instruction (except for FP Load/Store instructions).
- Address to a FP number (in memory) is stored in an INT register (address or pointer itself cannot be FP, a pointer is always a 32-bit integer address).
- It is not recommended to compare two FP values for equality in programs.
- In MIPS, floating point arithmetic is performed by a separate hardware chip, called Coprocessor 1 by tradition. The FP coprocessor has its own FP Arithmetic Unit and 32 floating-point registers, named $f0-$f31, as mentioned above. All these registers are stored in a separate register file. This means that both ALU and FPU can work on different instructions independently, thus increasing the throughput of the system.
- MIPS floating point hardware supports both single- and double-precision values. Just like integer registers, floating point registers are 32 bits long. Each register can hold a single-precision value or they can be combined to hold 16 double-precision numbers: $f0-$f1, $f2-$f3, …, $f30-$f31. However, in this lab we will only work with Single Precision FP Numbers.
- Floating-point programming conventions: Just like integer registers, code written for floating point needs to follow specific conventions (such as for procedure calls).
- Floating-point (input) arguments are placed in registers $f12-$f15
- Floating-point return values go into $f0-$f1
- Registers $f0- $f19 are caller-saved
- Registers $f20-$f31 are callee-saved
- All FP (single precision) instructions end with .s suffix in assembly. For example,
the instruction add.s $f0, $f1, $f2 for FP numbers, adds the contents of FP registers $f1 and $f2 and places the result in FP register $f0. Notice ‘.s’ is for single precision.
- Product of two FP numbers fits in a single 32-bit FP register unlike integer multiplication where the product fits in 64-bits
MIPS FLOATING POINT ASSEMBLY INSTRCUTIONS
Floating-point MIPS instructions are similar to the integer instructions, both in structure and syntax. Many instructions can be used directly, but with the ‘.s’ extension. For example,
mul.s $f7, $f9, $f21 # FP Multiplication
li.s $f5, 3.14159 # FP Load Immediate
2.1 Memory accesses MIPS FLOATING POINT ASSEMBLY INSTRCUTIONS
There are two instructions for data transfer. The format is:
l.s $f2, 0($a0) # Loading from Memory in to a FP Register: $f2=MEM[$a0]
s.s $f4, 4($s0) # Storing from a FP Register into Memory: MEM[$s0+4]=$f4
Note: The Base Address is always provided in an integer register since the address is an integer.
2.2 Moving Integer values to FP IN MIPS FLOATING POINT ASSEMBLY INSTRCUTIONS
There are two steps to transfer data to and from Integer registers and FP registers.
STEP-1: Instructions to transfer raw data between integer registers and floating-point ones.
mtc1 $t0, $f0 # move to Coprocessor1 result: $f0 = $t0
mfc1 $t0, $f0 # move from Coprocessor1 result: $t0 = $f0
STEP-2: The above instruction has copied bits but the result is still not in proper 32-bit Single Precision FP format. Instructions to convert contents of the source register to the destination register in the correct format are:
cvt.s.w $f2, $f2 # convert from word (stored in $f2) to single-precision (in $f2)
cvt.w.s $f2, $f2 # convert from single-precision (stored in $f2) to word (in $f2)
Conditional branches are performed in two steps. First, perform the comparison. Second, branch depending on the result. This is quite similar to the behavior of SLT instructions. For example, the instruction branch to LOOP if $f2 < $f4 can be written as:
c.lt.s $f2, $f4 # if ($f2 < $f4) set bit comp to 1, else set it to 0
bc1t LOOP # if (comp == 1) go to LOOP
Note that the first instruction can use any of the three possible comparisons (eq, lt, le) and can compare. Also, the name of the branch instruction is the abbreviation of “branch if the condition code of coprocessor1 is true” where true means set to 1.
Recall that the familiar set-if instructions use a programmer-specified register to store the result of the comparison. But since the result is either 0 or 1, one bit suffices to store the result. The branch instruction then uses the result produced by the previous instruction to decide what to do next.
Exercise 1: Reading a Floating Point Value using SYSCALL and examining
the Single Precision Binary Floating Point (32-bit) format:
Read the value of π = 3.1415 using SYSCALL ($v0 = 6, returns the value in $f0) as given in the reference table on Page 8. The code is given below:
.data Mesg1: .asciiz “Input a FP Number (Do not forget the dot): “ .text .globl main main: la $a0, Mesg1 li $v0, 4 syscall # Print the string “Mesg1” li $v0, 6 # Place 6 in $v0 to read a floating number syscall # FP number returned in $f0 End_Prog: li $v0, 10 syscall
After running this program and entering 3.1415 as input. Make the register display for FP as HEX and note the value of sign (MSB), EXP (8-bit biased) and mantissa (23-bit with implicit 1). Fill the following:
Hex Number in $f0: 0x40490e56
Its Binary value: 0 10000000 10010010000111001010110
Sign: 0, Exponent: 10000000, Fraction: 10010010000111001010110
Similarly, run this program again and this time enter −0.0005 as the input number and repeat the above task.
Hex Number in $f0: 0xba03126f
Its Binary value: 1 01110100 00000110001001001101111
Sign: 1, Exponent: 01110100, Fraction: 00000110001001001101111
Exercise 2: Procedure Call for a FP Program:
Write a MIPS code that asks for Fahrenheit temperature and returns the Celsius value which is printed on the console. The conversion formula is:
Celsius = (Fahrenheit − 32) ÷ 1.8
Write a procedure (function) “T_Convert” for this temperature conversion. Fahrenheit value is to be passed into the function as single precision data and Celsius value is to be returned in single precision format. Remember to follow all the floating point procedure conventions mentioned in Part-1, Point-8 (a,b,c,d). Give the Celsius values for the following Fahrenheit values:
|Temperature in Fahrenheit||Temperature in Celsius|
Code of the Program:
.data Input: .asciiz "\nPlease Enter the Temperature in Fahrenheit: " Output: .asciiz "\nThe Temperature in Celsius is: " .text .globl main main: la $a0, Input li $v0, 4 syscall # Print Input String li $v0, 6 syscall # Read Floating Point Number in $f0 li.s $f4, 0.0 # Initialize $f4 with 0.0 add.s $f12, $f4, $f0 # Move input number into $f12 as input parameter jal T_Convert # Call Procedure T_Convert with input in $f12 li.s $f4, 0.0 # Initialize $f4 with 0.0 add.s $f12, $f4, $f1 # Procedure returns result in $f1, move it to $f12 la $a0, Output li $v0, 4 syscall # Print Output String li $v0, 2 syscall # Print result contained in $f12 End_Prog: li $v0, 10 syscall # Exit to System T_Convert: # Procedure T_Convert li.s $f5, 1.8 # Load 1.8 in $f5 li.s $f6, 32.0 # Load 32.0 in $f6 sub.s $f1, $f12, $f6 # $f1 = Fahrenheit($f12) - 32 div.s $f1, $f1, $f5 # Celsius ($f1) = (Fahrenheit - 32) / 1.8 jr $ra # Return to Caller
Exercise 3: Average of a FP Array: Write a program to find and print the average value of a floating point array in the console area of PCSPIM. The assembler directive .float is used to initialize floating point numbers. Initial framework is given below:
.data myArray: .float 200.86, -100.0, 300.0, -150.7258, 400.68562, -200.5686, 54.6789, 481.9827, -682.4012, 586.4512 .text .globl main main: la $s0, myArray # FP Array Base Address in integer register $s0 li $t1, 0 # Loop index in integer register $t1 li $t2, 10 # Loop limit in integer register $t2 li.s $f12, 0.0 # Initialize FP register $f12 with 0.0 li.s $f13, 10.0 # Initialize FP register $f13 with 10.0 loop: sll $t3, $t1, 2 # Index × 4 add $s1, $s0, $t3 # Effective Array Address = Base Address + (Index × 4) l.s $f2, 0($s1) # Load FP Array Number into $f2 add.s $f12, $f12, $f2 # Accumulate the loaded FP Number in $f12 addi $t1, $t1, 1 # Loop Index++ bne $t1, $t2, loop # Branch to label loop if loop limit not reached div.s $f12, $f12, $f13 # Divide the accumulated value by 10 (number of elements) la $a0, Result li $v0, 4 syscall # Print string Result li $v0, 2 syscall # Print the result (Average Value of 10 FP Numbers) End_Prog: li $v0, 10 syscall # Exit to System
# Average Value is: 89.09628296 (As displayed on the Console Window)
Exercise 4: Printing the MIN of 12 user defined FP Numbers: Write a MIPS program to find the MIN of twelve floating point numbers. The user should be asked to input 12 FP numbers and the program should print the MIN value (SYSCALL with $v0 = 2 and FP number in $f12 – see the table on Page 7). You are not required to store these 12 values in any array, just take them as input from the user through the console and print the MIN.
.data Msg1: .asciiz "\nPlease enter the FP Number: " Msg2: .asciiz "\nThe Smallest FP Number is: " .text .globl main main: li $t0, 0 # Loop index in integer register $t0 li $t1, 12 # Loop limit in integer register $t1 la $a0, Msg1 # Load the Base Address of String Msg1 li.s $f2, 0.0 # The FP register $f2 will be used to hold MIN, Initialize to 0.0 li.s $f3, 0.0 # Initialize FP register $f3 with 0.0 Input_Loop: beq $t0, $t1, Print # Branch to label Print if loop limit reached addi $t0, $t0, 1 # Loop Index++ li $v0, 4 # Place 4 in $v0 syscall # Print String Msg1 li $v0, 6 # Place 6 in $v0 syscall # Read FP Number into FP register $f0 c.lt.s $f2, $f0 # if ($f2 < $f0) set bit comp to 1, else set it to 0 bc1t Input_Loop # if (comp == 1) go to Input_Loop (No need to update MIN) add.s $f2, $f0, $f3 # else move new MIN from $f0 to $f2 j Input_Loop # Jump to Input_Loop Print: add.s $f12, $f2, $f3 # Move final MIN value from $f2 to $f12 la $a0, Msg2 # Load the Base Address of String Msg2 li $v0, 4 # Place 4 in $v0 syscall # Print String Msg2 li $v0, 2 # Place 2 in $v0 syscall # Print the contents of $f12 (MIN) End_Prog: li $v0, 10 syscall