Working with MIPS Floating Point Architecture using PCSPIM

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

  1. 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.
  2. $f0 behaves like any other register, unlike $0, which is always zero. $f0 can be used as a destination register.
  3. You cannot mix floating-point registers and integer registers in a single instruction, except for floating-point load/store instructions.
  4. 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

  1. 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.
  2. 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.
  3. 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.

MIPS Floating-Point Instructions

  1. All floating-point (single-precision) instructions end with the .s suffix in assembly. For example, the instruction add.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.
  2. 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.
  • 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.

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.
  • 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.

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.
  • 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.

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 FahrenheitTemperature in Celsius
320.00000000
212100.00000000
98.637.00000000
12250.00000000
14965.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:

Leave a Comment