CST 250 Lab 3 -- Basic Subroutines
Lab Description

     In this lab we will write and test a program that will traverse a string stored in the .data segment using the .asciiz directive, and count the total number of decimal digits “0” to “9” within the string. It will also compute a sum of all the digits it encounters. To do this, write a main routine that traverses the characters of the string one by one and for each character calls a subroutine called is_a_digit. If the passed character is not a digit, is_a_digit returns -1, otherwise it returns the actual value of the digit (i.e. 0 through 9). If the returned value is not -1, the main routine should increment a count value and it should also add the digit to an accumulating sum. These values will ultimately be saved in .word variables called "dCount" and "dSum" respectively (just before the program ends in an infinite loop). Also note that "Count" is the name of a co-processor register, using it as a variable name will confuse the debugger.

Calling Convention

     Note that you do not need to create stack frames for this lab.  The topic of stack frames will be introduced shortly in class if it hasn't been already.  However, you will have to follow the convention about which registers are preserved and not preserved across a call. That is, we must assume that the is_a_digit subroutine can freely modify any call-scratch registers (a0 - a3, v0, v1, and t0 - t9) but must preserve and restore the value of any other registers that it uses (on the stack). Be careful to use s registers for values that need to be preserved across any subroutine calls. These are often referred to as "live" values.
      As discussed in class, the result of the calling convention is that any call-preserved registers (s0 - s7, ra, fp, gp) that are used in a routine (main or any subroutines it calls) need to be saved on the stack at the beginning of the routine and restored at the end of the routine. Also, any call-scratch registers that hold live values and need to be preserved across a call need to be saved just before the call and restored before that value is needed again (usually just after the call returns). Because calls are often inside loops, there will be more saving and restoring of call-scratch (t) registers than there would be if call-preserved (s
)registers were used for live values. Since our programs will not need many live values, I will deduct points for using t registers for live values. However, it is perfectly fine (and in fact preferable) to use t (and other call-scratch) registers for any values that are not live. Also note that sp is also a call preserved register, but its value is preserved by making sure we release the same amount of space at the end of a routine as we allocated at the beginning. I will check to make sure the sp is returned (at the end of main) to the same value it had at the beginning of main.

     Remember that the calling convention allows us to write functions so that they could be used by someone else who understands the calling convention and what is passed and returned (i.e, the functions could be placed in a library). Don't be tempted to say "I know that my is_a_digit routine doesn't use t9, so I can use t9 for a live value in  my main routine".   As you work with more functions in a program it becomes a nightmare to keep track of which functions use which registers.  Further, if the is_a_digit routine was a library routine, you would not have any way of knowing what registers it used.  Also note that the is_a_digit routine will be used in a future lab.

Function is_a_digit:
      As noted above, this function is used to determine if an ASCII character passed to it represents a decimal digit and returns its value if it is. If the character is not a digit, the function returns -1 (0xffffffff).  Determining if a character is a digit can be done by range checking.  The ASCII code for the digits “0” to “9” are the hex values 0x30 to 0x39.  Pseudo-code for is_a_digit is shown below.

int is_a_digit(char c) {
    if (c >= 0x30 &&
c <= 0x39) {
       // return the binary value for ASCII character digit
          return (c - 0x30); 
    } else return (-1);
}

      Following the calling convention, c (the character to be tested) will be passed in a0 and the result returned in v0. Also, following the calling convention, since this function does not call any subroutines (and therefore does not have any live values), it would be best to use all call-scratch (t) registers in it.

Procedure:
      Create C-style pseduo-code for the main routine and include this in a comment block at the beginning of main. Then build and test main using your pseudo-code as a guide (adjust your pseudo-code if necessary) to see that it traverses the string. Remember that since we are traversing the string character by character from beginning to end, a pointer based approach for this is appropriate. Add comments (as appropriate) as you go. Consider which values will need to be preserved across the call to is_a_digit and use s registers for these. Also make sure that your main routine saves (on the stack) and restores any call-preserved (s) registers that it uses. Include in this the saving and restoring of ra (since main will call is_a_digit and thus modify ra) even though we don't actually return from main. As each register is saved, add a comment indicating what live value this register will be used for. Then add and test the is_a_digit function and any other functionality. Use .ent is_a_digit and .end is_a_digit assembler directives to indicate the beginning and the end of the is_a_digit function. Also do this for main using .ent main and .end main . Please make it a habit to use .ent and .ext in future labs as well.

Deliverables: