A bit of a smaller lab this week, since the project will be out soon. So, get this lab out of the way quick so you’ll have more time for the project!

In this lab, you’ll be using arrays, functions, and for loops. You’ll also get a taste of that “top-down design” I talked about in class. Once you do this lab, you’ll know everything you need to do the first project.

When complete, your lab will ask for 5 numbers, putting them into an array; then it will print that array out; and finally, print out the characters of a string:

enter value: 10
enter value: 30
enter value: 20
enter value: 40
enter value: 60
arr[0] = 10
arr[1] = 30
arr[2] = 20
arr[3] = 40
arr[4] = 60
t
e
s
t
i
n
g
!

-- program is finished running --

0. Getting started

Here’s the starting code, including the print_str macro you got in lab 2.

# YOUR NAME
# YOUR USERNAME

# preserves a0, v0
.macro print_str %str
	# DON'T PUT ANYTHING BETWEEN .macro AND .end_macro!!
	.data
	print_str_message: .asciiz %str
	.text
	push a0
	push v0
	la a0, print_str_message
	li v0, 4
	syscall
	pop v0
	pop a0
.end_macro

# -------------------------------------------
.eqv ARR_LENGTH 5
.data
	arr: .word 100, 200, 300, 400, 500
	message: .asciiz "testing!"
.text
# -------------------------------------------
.globl main
main:

	# exit()
	li v0, 10
	syscall
# -------------------------------------------

Small tangent: named constants

Look above the .data line in the starting code. You see:

.eqv ARR_LENGTH 5

The Java equivalent would be something like:

public static final int ARR_LENGTH = 5;

This is a named constant. Named constants are wonderful. Names are wonderful. Whenever you have some “magic value” that has specific importance, don’t write it inside your code. Make a named constant for it.

Also, don’t just write the value of the named constant when you need it. Use the name!:

MysteriousObvious
# 5? why 5? what is 5?
blt t0, 5, _top
# ohh, we're looping over an array
blt t0, ARR_LENGTH, _top

Why do we do this? Not only is it more readable, it means we can change the length of the array in the future, and none of the code that depends on the length has to be changed.


1. Stubbing out the program

Look at the expected output at the top of the page. We have 3 broad steps in this program:

  1. ask the user for ARR_LENGTH numbers, putting them into an array
  2. print out the contents of the array
  3. print out the characters of a string

So, let’s make three stub functions (empty functions) for these three steps.

  1. Make three empty functions named input_arr, print_arr, and print_chars.
    • You start a function with a label, and then end it with jr ra, like:
        input_arr:
            # all the code will go here
            jr ra
      
    • You should probably use comments to visually separate those functions.
  2. Call them all from main using jal.
    • So, you are making main do this:
        void main() {
            input_arr();
            print_arr();
            print_chars(message);
            exit(); // already there for you
        }
      
    • Wait, print_chars has an argument?
      • Yeah. So do la a0, message before jal print_chars. That’s how we pass em!
  3. Assemble and run.
    • If it does nothing, and exits, that’s good! We’re just testing that we wrote things correctly.
    • If it doesn’t exit, something is wrong…

2. Printing the array

Remember the slide on for loops from the Arrays lecture? Go find that…

First let’s just try printing the array. The array has some values in it by default, so when we get this working, our program will output:

arr[0] = 100
arr[1] = 200
arr[2] = 300
arr[3] = 400
arr[4] = 500
  1. Inside print_arr (where else?), make a for loop using t0 as the index variable.
    • It should do the equivalent of for(t0 = 0; t0 < ARR_LENGTH; t0++)
    • Remember, you have to name your control flow variables with an underscore _ at the beginning.
    • Now test it!
      • You could step through it…
      • You could put print_str "test\n" inside it and make sure it prints 5 times…
      • Just make sure it loops right.
  2. Inside that loop, you need to do the equivalent of this code:
     print_str("arr[");
     print_int(t0);
     print_str("] = ");
     print_int(arr[t0]); // !!
     print_str("\n");
    
    • The print_int lines above are syscall 1 which you’ve been using.
      • Remember that you can copy registers with move.
    • One line indexes arr using t0 as the index…
      • You have to use that A + S*i formula.
      • Have a look at the slides for a refresher.
      • What copies from memory (where the array is) to a register?

Get the above working! Don’t move on until you do!


3. Filling the array with user input

Now we can work on input_arr. You’re on your own for this one, but you know everything you need now. This is what it should do:

void input_arr() {
    for(int i = 0; i < ARR_LENGTH; i++) {
        print_str("enter value: ");
        arr[i] = syscall_5();
    }
}

Done correctly, your program should ask for 5 numbers and then print them out.

enter value: 10
enter value: 30
enter value: 20
enter value: 40
enter value: 60
arr[0] = 10
arr[1] = 30
arr[2] = 20
arr[3] = 40
arr[4] = 60

4. Printing the characters of a string

Strings are arrays of characters. A character is just an integer with a special meaning.

ASCII is a way of encoding strings where each character is one byte. The .asciiz directive makes a zero-terminated ASCII string. That means that the end of the string is marked with a byte with value 0. Not the character '0', an actual 0 value.

If I wrote .asciiz "ABC012", this string is 6 characters long, but it would take up 7 bytes in memory:

ASCII 'A' 'B' 'C' '0' '1' '2' '\0'
Decimal 65 66 67 48 49 50 0

So to print an ASCII string character-by-character, it works something like this (DON’T TRANSLATE THIS, JUST READ IT):

// notice the condition!
for(int i = 0; str[i] != 0; i++)
    print_char(str[i])

In some cases, iterating over an array in assembly can be done in a more natural way with something I like to call the “walking pointer” technique.

Instead of translating str[i] inside the loop, we have a pointer which points at the current item in the array. Before the loop, we make that pointer point to the first item. On each iteration, we move to the next item. Like this:

So. Here’s what I want you to do:

  1. Comment out the jal input_arr and jal print_arr while you’re working on print_chars.
    • See, isn’t that nice? Two #s and your program’s behavior changes a lot.
  2. Inside print_chars, you’re going to do this:
     void print_chars(str) {
         // move it out of the argument register
         t0 = str;
    
         // *t0 reads as "the value at t0"
         // that is, the value you get when you load a byte from (t0)
         while(*t0 != 0) {
             // syscall 11
             print_char(*t0);
             print_char('\n');
    
             // how many bytes is 1 byte?
             t0++;
         }
     }
    
    • The *t0 above will be translated as lb __, (t0), where __ will be some other register.
    • We’re moving a0 into t0 because we have to reuse a0 to do the system call.
  3. Test. Step through it if it doesn’t work.
    • Check the “ASCII” box in the bottom of the “Data Segment” window to see the ASCII strings in memory.
    • Check the “Hexadecimal Values” box and watch the value of t0 as it steps through the string.
    • It should start at whatever address message starts at, and then count up from there.
    • Remember this is the goal:
        t
        e
        s
        t
        i
        n
        g
        !
      
        -- program is finished running --
      
  4. Don’t forget to uncomment those jals in main!

Submitting

First, be sure you uncomment things if you had them commented for testing. Your program should behave as described at the top.

Then, make sure your file is named correctly. My username is jfb42, so:

Submit here.

Drag your asm file into your browser to upload. If you can see your file in the folder, you uploaded it correctly!

You can also re-upload if you made a mistake and need to fix it.