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.

Task

Write a MIPS program using a recursive subprogram to prompt a user for two integers, m and n, then compute and print the combination of n things taken m at a time.

Deliverables

Send me an email with the MIPS file containing your program. Comment each line and code segment explaining its function.

References

  1. Binomial Coefficient from MathWorld.
  2. Programmed Introduction to MIPS Assembly Language

Have fun!


Copyright © 2017, David A. Reimann. All rights reserved.