Until now, we have only covered integer computations in PCSPIM in our previous tutorials. However, in reality, computations also involve floating-point arithmetic. For this purpose, MIPS supports various floating-point instructions along with 32 special floating-point registers, $f0
to $f31
. Below are some interesting facts about floating-point organization and floating-point programming in MIPS architecture. Understanding these facts will help you work with floating-point numbers in the MIPS architecture.
MIPS Floating-Point Registers
- There are thirty-two 32-bit registers dedicated to floating-point operations. These registers are separate from the integer registers (
$0
to$31
) that you have worked with before. $f0
behaves like any other register, unlike$0
, which is always zero.$f0
can be used as a destination register.- You cannot mix floating-point registers and integer registers in a single instruction, except for floating-point load/store instructions.
- The address of a floating-point number in memory is stored in an integer register. Pointers are always represented as 32-bit integer addresses.
Floating-Point Arithmetic in MIPS
- In MIPS, floating-point arithmetic is performed by a separate hardware chip called Coprocessor 1. The FP coprocessor has its own FP Arithmetic Unit and 32 floating-point registers, named
$f0
to$f31
. These registers are stored in a separate register file. This allows both the ALU and FPU to work on different instructions independently, increasing the system’s throughput. - MIPS floating-point hardware supports both single-precision and double-precision values. Floating-point registers are 32 bits long, just like integer registers. 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, for this lab, we will only work with single-precision floating-point numbers. - Floating-point programming conventions are similar to integer registers. Code written for floating-point operations 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.
- Floating-point input arguments are placed in registers
MIPS Floating-Point Instructions
- All floating-point (single-precision) instructions end with the
.s
suffix in assembly. For example, the instructionadd.s $f0, $f1, $f2
adds the contents of floating-point registers$f1
and$f2
and places the result in register$f0
. The suffix.s
is used to indicate single precision. - The product of two floating-point numbers fits in a single 32-bit floating-point register, unlike integer multiplication, where the product fits in a 64-bit register.
MIPS Floating Point Assembly Instructions
Floating-point MIPS instructions are similar to integer instructions in structure and syntax, but with the .s
extension. For example:
mul.s $f7, $f9, $f21 # FP Multiplication
li.s $f5, 3.14159 # FP Load Immediate
Memory Access Instructions
There are two instructions for data transfer:
- Loading from Memory into a Floating-Point Register:
- Syntax:
l.s $f2, 0($a0)
- Description: Loads the floating-point number at the memory address stored in register
$a0
into register$f2
.
- Syntax:
- Storing from a Floating-Point Register into Memory:
- Syntax:
s.s $f4, 4($s0)
- Description: Stores the value in register
$f4
into memory at the address$s0 + 4
.
- Syntax:
Note: The base address for memory access is always provided in an integer register since the address is represented as an integer.
Moving Integer Values to Floating Point
Transferring data between integer registers and floating-point registers involves two steps.
Step 1: Instructions to transfer raw data between integer registers and floating-point registers:
- Move to Coprocessor1:
- Syntax:
mtc1 $t0, $f0
- Description: Moves the value in integer register
$t0
to Coprocessor 1, resulting in the value stored in floating-point register$f0
.
- Syntax:
- Move from Coprocessor1:
- Syntax:
mfc1 $t0, $f0
- Description: Moves the value from Coprocessor 1 to integer register
$t0
, resulting in the value stored in floating-point register$f0
.
- Syntax:
Step 2: Instructions to convert the contents of source registers to the destination registers in the correct format:
- Convert from Word to Single Precision:
- Syntax:
cvt.s.w $f2, $f2
- Description: Converts the word stored in register
$f2
to single-precision format, storing the result in$f2
.
- Syntax:
- Convert from Single Precision to Word:
- Syntax:
cvt.w.s $f2, $f2
- Description: Converts the single-precision value stored in
$f2
to word format, storing the result in$f2
.
- Syntax:
Branches in Floating Point
Conditional branches in floating-point operations consist of two steps: performing the comparison and branching based on the result. This behavior is similar to that of SLT instructions. For example, branching to the LOOP
label if $f2
is less than $f4
can be written as follows:
c.lt.s $f2, $f4 # if ($f2 < $f4) set the comparison bit to 1, else set it to 0
bc1t LOOP # if (comp == 1) go to LOOP
The first instruction can use any of the three possible comparisons (eq
, lt
, le
) and can compare any two floating-point registers. The name of the branch instruction is an abbreviation for “branch if the condition code of Coprocessor 1 is true,” where true means the condition code is set to 1.
Please note that the familiar set-if instructions store the result of the comparison in a programmer-specified register, whereas floating-point branches only require one bit to store the result (0 or 1). The branch instruction then uses the result produced by the previous instruction to decide what to do next.
MIPS Floating Point Assembly Examples
In this section, we will see assembly language examples using MIPS floating point instructions.
Exercise 1: Reading a Floating Point Value using SYSCALL
Read the value of π (3.1415
) using SYSCALL ($v0 = 6
, which returns the value in $f0
).
.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, check the register display for the floating-point value in hexadecimal format. Take note of the value of the sign (MSB), exponent (8-bit biased), and mantissa (23-bit with implicit 1
). Fill in the following information:
- Hex Number in
$f0
:0x40490e56
- Binary Value of
$f0
:0 10000000 10010010000111001010110
- Sign:
0
, Exponent:10000000
, Fraction:10010010000111001010110
Repeat the process by running the program again, this time entering -0.0005
as the input number, and repeat the steps to obtain the hexadecimal and binary representation of the floating-point value.
- Hex Number in
$f0
:0xba03126f
- Binary Value of
$f0
:1 01110100 00000110001001001101111
- Sign:
1
, Exponent:01110100
, Fraction:00000110001001001101111
Exercise 2: Procedure Call for a Floating-Point Program
Write a MIPS code that asks for a Fahrenheit temperature from the user and returns the Celsius equivalent, which is then printed on the console. The conversion formula is:
Celsius = (Fahrenheit - 32) / 1.8
Write a procedure (function) named T_Convert
to handle this temperature conversion. The Fahrenheit value should be passed as single-precision data, and the Celsius value should be returned in single-precision format. Ensure you follow all the floating-point procedure conventions mentioned in Part 1, Point 9 (a, b, c, d). The Celsius values for the following Fahrenheit values should be printed:
Temperature in Fahrenheit | Temperature in Celsius |
---|---|
32 | 0.00000000 |
212 | 100.00000000 |
98.6 | 37.00000000 |
122 | 50.00000000 |
149 | 65.00000000 |
The code for the program is as follows:
.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 # Place 6 in $v0 to read a floating-point number
syscall # Read the temperature in Fahrenheit into $f0
li.s $f4, 0.0 # Initialize $f4 with 0.0
add.s $f12, $f4, $f0 # Move the input number into $f12 as the input parameter
jal T_Convert # Call the procedure T_Convert with the input in $f12
li.s $f4, 0.0 # Initialize $f4 with 0.0
add.s $f12, $f4, $f1 # The procedure returns the result in $f1, move it to $f12
la $a0, Output
li $v0, 4
syscall # Print Output String
li $v0, 2
syscall # Print the 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 into $f5
li.s $f6, 32.0 # Load 32.0 into $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 an Array of Floating-Point Numbers
Write a program to find and print the average value of a floating-point array on the console area of PCSPIM. Initialize the array using the assembler directive .float
.
.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
The average value should be displayed on the console window: 89.09628296
.
Exercise 4: Printing the Minimum of 12 User-Defined Floating-Point Numbers
Write a MIPS program that finds and prints the minimum of twelve floating-point numbers. The user should be prompted to enter 12 floating-point numbers, and the program should print the minimum value using SYSCALL with $v0 = 2
and the floating-point number in $f12
.
The code for the program is as follows:
.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 hold the MIN value, 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 the floating-point number into FP register $f0
c.lt.s $f2, $f0 # If ($f2 < $f0), set the comparison bit 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 the new MIN from $f0 to $f2
j Input_Loop # Jump to Input_Loop
Print:
add.s $f12, $f2, $f3 # Move the 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 # Exit to System
To run the program, enter 12 floating-point numbers when prompted, and the program will display the smallest number.
Conclusion
In conclusion, understanding floating-point arithmetic and programming in MIPS architecture is crucial for working with computations that involve floating-point numbers. MIPS provides dedicated floating-point registers and instructions to perform arithmetic operations efficiently. By utilizing the floating-point coprocessor and adhering to specific conventions, programmers can handle single-precision floating-point values with precision and accuracy. Examples such as reading a floating-point value using SYSCALL, temperature conversion, finding the average of an array, and printing the minimum of user-defined numbers demonstrate the practical application of floating-point programming in MIPS. By mastering these concepts and techniques, programmers can harness the power of floating-point arithmetic in MIPS architecture to solve complex computational tasks effectively.
Related content: