CS 354 Computer Organization and Systems Fall 2017

## Project 10 - Using Subroutines and Recursion in MIPS to compute binomial coefficients

### Goals

In this lab you will gain familiarity with the MIPS assembly programming related to subroutines (also called functions and methods) and recursion. You will do this by writing a MIPS program to compute binomial coefficients using a recursive formula.

### Subroutines in MIPS

Section A.6 of our text describes how one can use subroutines (also called procedures, functions, and methods) in MIPS. Because of the importance of subroutines in modern programming, most hardware designers include mechanisms to help programmers. In a high-level language, like C or Java, most of the details of subroutine calling are hidden from the programmer.

MIPS has special registers to send information to and from a subroutine. The registers \$a0, \$a1, \$a2, \$a3 are used to pass arguments (or parameters) into the subroutine. The registers \$v0 and \$v1 are used to pass arguments (or parameters) back from the subroutine.

The stack (and stack pointer register \$sp) is used for a variety of things when using subroutines. The stack is used to pass additional parameters to and from subroutines. It is also used to hold temporary values in memory that a subroutine may need. Most importantly, it is used to save the current state so the subroutine can return back to the caller once it has completed. This include the frame pointer (\$fp), the return address register (\$ra), and the callee-saved registers (\$s0-\$s7).

Imagine you would like a program that reads two integers from the user, determine the largest of the two, the prints the maximum value. One way to do this would be to have a subroutine that takes two arguments and returns the larger of the two. The program max.s shown below illustrates one way to do this. Download and run this program, stepping through the program and watch the memory and the register values change.

 ``` # Determines the maximum of two integers # using a funtion # David A. Reimann # April 2008 .text main: # Create stack frame using calling convention addi \$sp,\$sp,-32 # Stack frame is 32 bytes long sw \$ra,20(\$sp) # Save return address sw \$fp,16(\$sp) # Save old frame pointer addi \$fp,\$sp,28 # Set up frame pointer # Get numbers from user li \$v0, 4 # Load 4=print_string into \$v0 la \$a0, p1 # Load address of first prompt into \$a0 syscall # Output the prompt via syscall li \$v0, 5 # Load 5=read_int into \$v0 syscall # Read an integer via syscall add \$s0, \$v0, \$zero # Copy from \$v0 to \$s0 li \$v0, 4 # Load 4=print_string into \$v0 la \$a0, p2 # Load address of second prompt into \$a0 syscall # Output the prompt via syscall li \$v0, 5 # Load 5=read_int into \$v0 syscall # Read an integer via syscall add \$s1, \$v0, \$zero # Copy from \$v0 to \$s1 # Compute maximum add \$a0,\$s0,\$0 # Put argument (\$s0) in \$a0 add \$a1,\$s1,\$0 # Put argument (\$s1) in \$a1 jal maximum # Call maximum function, result in \$v0 # Output results add \$a0, \$v0, \$zero # Load sum of inupt numbers into \$a0 li \$v0, 1 # Load 1=print_int into \$v0 syscall # Output the prompt via syscall # Remove Stack Frame lw \$ra,20(\$sp) # Restore return address lw \$fp,16(\$sp) # Restore frame pointer addi \$sp,\$sp,32 # Pop stack frame # Exit li \$v0, 10 # exit syscall # maximum function to compute max(\$a0, \$a1) maximum: bge \$a0, \$a1, L1 # is \$a0 >= \$a1 ? add \$v0, \$a1, \$0 # \$a1 is max j return L1: add \$v0, \$a0, \$0 return: jr \$ra # Return to caller .data .align 0 p1: .asciiz "Please enter the 1st integer: " p2: .asciiz "Please enter the 2nd integer: " ```

Here are some important things to note about the above program. Because the stack starts at a high address and grows by growing towards lower addresses, pushing items on the stack require decrementing the corresponding stack pointer address. The \$ra register stores the address where a subroutine should return when it is completed; it is set automatically by the jal instruction to be the address of the instruction following the jal instruction. In the subroutine, the jr instruction sets the program counter to be the address located in the \$ra register.

### Recursion in MIPS

One classical example of recursion is the factorial function. The program factorial.s shown below illustrates one way to do this. This is very similar to the example in section A.6. Download and run this program, stepping through the program and watch the memory and the register values change.

 ``` # Computer n! recursively in MIPS # Based on the example in Appendix A # In fact it is almost identical .text main: # Create stack frame using calling convention addi \$sp,\$sp,-32 # Stack frame is 32 bytes long sw \$ra,20(\$sp) # Save return address sw \$fp,16(\$sp) # Save old frame pointer addi \$fp,\$sp,28 # Set up frame pointer # Compute 10! li \$a0,10 # Put argument (10) in \$a0 jal fact # Call factorial function # Output result add \$a0, \$v0, \$zero # Load sum of inupt numbers into \$a0 li \$v0, 1 # Load 1=print_int into \$v0 syscall # Output the prompt via syscall # Remove Stack Frame lw \$ra,20(\$sp) # Restore return address lw \$fp,16(\$sp) # Restore frame pointer addi \$sp,\$sp,32 # Pop stack frame # Exit li \$v0, 10 # exit syscall # Factorial function to compute n! fact: # Create stack frame addi \$sp, \$sp, -32 # Pop stack sw \$ra,20(\$sp) # Save return address sw \$fp,16(\$sp) # Save frame pointer addi \$fp,\$sp,28 # Set up frame pointer sw \$a0,0(\$fp) # Save argument (n) # See if we need recursive step lw \$v0,0(\$fp) # Load n bgtz \$v0,L2 # Branch if n > 0 li \$v0,1 # Return 1 if n = 0 j L1 # Jump to code to return # Compute (n-1)! L2: lw \$v1,0(\$fp) # Load n addi \$v0,\$v1,-1 # Compute n - 1 move \$a0,\$v0 # Move value to \$a0 jal fact # Call factorial function lw \$v1,0(\$fp) # Load n mul \$v0,\$v0,\$v1 # Compute (n-1)*n = n! # Remove Stack Frame and return L1: # Result is in \$v0 lw \$ra, 20(\$sp) # Restore \$ra lw \$fp, 16(\$sp) # Restore \$fp addiu \$sp, \$sp, 32 # Pop stack jr \$ra # Return to caller ```

Here are some important things to note about the above program. As in any high-level language, recursion requires some careful thinking about what is happening. Perhaps seeing what happens at the assembly language level will help you understand recursion better.

### Binomial Coefficients

The of combinations of n things taken m at a time, denoted C(n,m), assuming n >= 0 and m >= 0, is given by

```C(n,m) = 1                        if n = m
= 1                        if m or n = 0
= C(n-1, m) + C(n-1, m-1)  otherwise.
```
You can use the formula
```C(n,m) = n!/((n-m)! m!)
```
to test you function for small values of n and m. Note this function defines Pascal's triangle:
```             1
1   1
1   2   1
1   3   3   1
1   4   6   4   1
1   5  10  10   5   1
. . . . . . . . . . . . . .
```
where each value is the sum of the two values above it. Note C(5,3) = 10, which says given 5 things, you can for 10 possible subsets of 3 elements.