In this lab, you’ll be making and using functions to extend what you did in the previous lab. (But if you didn’t finish that lab, don’t worry; I’m giving you everything you need!)

Refer back to the previous lab if you need to re-familiarize yourself with the way the display works.


0. Getting started

Right click and save this link.

Rename it correctly and put your full name and username at the top.

Open it in MARS, start the Keypad and LED Display plugin, connect it, assemble, and run. You should see this:

What’s this code doing? I wrote a new draw_horiz_line function that takes four arguments:

Look in main. I’ve called it three times, with all the arguments the same except for the Y coordinate. So I’m drawing a 10x3 rectangle. But that’s repetitive!

Instead, let’s write a new draw_rectangle function which calls draw_horiz_line repeatedly to draw the rectangle.


1. First bump in the road

Let’s move the code from main into the empty draw_rectangle at the bottom.

  1. Cut the code (ctrl+x / cmd+x) between the vvvvv and ^^^^^ lines out of main.
    • Then paste it (ctrl+v / cmd+v) into draw_rectangle (before the jr ra).
  2. Where the code used to be in main, replace it with jal draw_rectangle.
    • Make sure you put the jal in exactly the same spot as where the code was!
  3. Assemble and run.

Uh oh, the program is still running. Hit the pause button.

It’s at the jr ra at the end of draw_rectangle. If you hit the step button, it never moves. It’s an infinite loop!

Look what’s in ra and pc. Remember that jr ra just copies ra into pc.

Think about why this is happening.

Now fix this issue. Remember that any push and pop instructions should only ever be used at the beginnings and ends of functions. Don’t do them before/after jals!


2. Arguments

Instead of drawing a fixed-size rectangle, we want draw_rectangle to take arguments for the x, y, width, and height. (We’ll come back to the color.)

So, to reproduce the current rectangle - top-left at (5, 5), 10 pixels wide, 3 pixels tall - we want to call our function like draw_rectangle(5, 5, 10, 3).

First, we’ll make our function just draw one line using the arguments given.

  1. In main, change the call to draw_rectangle() to draw_rectangle(5, 5, 10, 3).
    • The aaaaaaaaaaaaarguments go in which registers?
    • you have to put the arguments there before the jal
  2. Change your draw_rectangle function so it does this (we’re ignoring height for now):
     void draw_rectangle(x, y, w, h) {
         draw_horiz_line(x, y, w, COLOR_RED);
     }
    
    • You should make a comment at the top of the function saying what the arguments are.
      • You can follow my example from draw_horiz_line!
    • x, y, and w are already in the correct argument registers, so you don’t have to do anything for them.
  3. Now it draws a single horizontal red line:

    • That’s fine! we haven’t made a loop to draw the rest yet.
  4. Add some more calls to draw_rectangle in main to test that the arguments are working:
     draw_rectangle(0, 10, 64, 5);   // spans width of screen
     draw_rectangle(54, 54, 10, 10); // square in bottom right corner
    

    It should look like this now:


3. Second bump in the road

Now we want to make it actually draw a rectangle.

  1. Change draw_rectangle’s code to this:

     do {
         draw_horiz_line(x, y, w, COLOR_RED);
         y++; // move to next line
         h--; // decrease count of lines
     } while(h > 0);
    
    • Remember to put the loop between the push and pop lines. (those are like your function’s braces.)
  2. This looks like it should work, but the output is the same as before. To try and figure out why, you can use a breakpoint:
    • Assemble your code.
    • In the labels window, find your draw_rectangle function’s loop label. Double click it.
    • It will highlight a row in the “Text Segment” window. That’s the instruction at that address.
    • Check the box to the left of that instruction, in the Bkpt column:
  3. Now, when you run, it will pause execution right before it runs the instruction at the breakpoint.
    • When it pauses, look at what’s in the argument registers.
      • Uncheck “Hexadecimal Values” in the “Data Segment” window to make it easier to read.
    • The first time, you’ll see 5, 5, 10, 3. That’s right!
    • You can then hit run again to resume, and it will keep stopping at that line.
      • This is super useful for diagnosing loop problems, since you don’t have to step through the whole loop every time.
    • But if you keep running, you’ll see that line is only run three times!!
      • That’s not right. It should run h times for each rectangle!
    • This is happening because we are breaking an important calling convention rule:

After jal draw_horiz_line, we don’t know what’s in the a, v, or t registers.

The arguments passed to draw_rectangle are sent to draw_horiz_line… and their values are lost.

This is one of the most common mistakes I see people make. Please remember this rule!

The solution is not to change which registers you use in each function.

Never ever do that. There are not enough registers for every function to get its own.


4. Solving it the right way

draw_rectangle has values which need to last across a jal. This is when we use the s registers.

As long as everyone follows the calling convention, s registers will have the same values after a jal as before.

To recall the rules: if you want to use an s register in function f

It’s kind of like declaring a local variable in an HLL. Except you also have to un-declare it ;)

So here’s what I want you to do:

  1. In draw_rectangle, copy the a0..a3 registers into s0..s3.
    • That means you have to push those 4 s registers at the beginning…
    • …and pop them at the end. In the right order.
    • And after the pushes, move the values from a0..a3 to s0..s3.
      • Yes, it will be a lot of lines. 5 pushes, 4 moves, and 5 pops.
      • Don’t worry.
  2. Then, you’ll have to set all four a registers when calling draw_horiz_line.
    • move the values from your s registers for a0..a2.
  3. Finally, change your y++ and h-- to increment the s registers that hold those values now.

Drumroll…

If the gap between the little rectangle and the wide rectangle looks smaller than this, you probably used bge instead of bgt in your loop. Change it!


“You mean we have to do this for every function???”

WELL…. yes and no.

Most functions don’t have this many arguments or local variables. In my experience I rarely need more than 1 or 2 s registers, and many functions I don’t need any. 1 extra push and 1 extra pop is not bad.

Also, once you’re more comfortable doing this, I’m going to give you something to help shorten this code. But not yet. Learn the calling convention first.

And that’s it! Small lab. Project is due next week, after all. ;) But if you want to go further, check the “try this” at the bottom.


Submitting

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.

Try this: draw_rectangle is hard-coded to use COLOR_RED, but really it should take that as an argument. But that’s 5 arguments!

In MIPS, arguments after the fourth are pushed onto the stack. So in main,

li  a0, 5
li  a1, 5
li  a2, 10
li  a3, 3
# fifth argument!
li  t0, COLOR_BLUE
push t0
jal draw_rectangle

Then inside draw_rectangle, you can access it by loading from (sp). You could use s4.