This week’s lab is a bit of a departure from the usual “printing text on a console” that I’m sure everyone just loves. Instead, we’ll be making an interactive graphical program. Yes, really!

For your first project, you’ll make an interactive video game. This lab has all the same parts as a game, but very simplified. The way any real-time interactive program works is like this:

  1. check for user inputs
  2. respond to those inputs by updating your program state
    • “state” means “your variables, data structures, etc.”
  3. change the output (screen) to reflect the new state
  4. wait for a little while
  5. loop back to step 1

You’ve probably done simple games in programming classes before: user’s turn, get inputs, opponent’s turn, update variables, loop again. The difference now is… it’s faster. The player and opponent(s) get 60 turns every second! AAAAAH!


Memory-mapped input and output

Syscalls are one way of making our programs interact with the real world. But syscalls only work when you have an operating system to “answer” the syscall. Most asm programming today is for embedded devices. These devices have no operating system, and therefore no syscalls.

If you want to do any input and output on these systems, you must access the hardware directly with memory-mapped input and output, or MMIO for short.

Normally, loads and stores copy data between the CPU and RAM. But with MMIO, certain “magical” addresses refer to devices, not RAM. When you access memory at these “magical” addresses, the data is transferred between the CPU and the hardware device, instead of between the CPU and RAM. That’s it!


Our imaginary hardware device

In MARS, go to to Tools > Keypad and LED Display Simulator. Not Keyboard and Display MMIO Simulator. This will pop up a window. Click the “Connect to MIPS” button in the bottom left.

     

Once it’s connected, you don’t have to close the window or reconnect it. You can re-assemble and re-run your program as many times as you want while the display is open.


0. Getting started

Right-click and “save link” or “download link” both of these files into the same directory:

Make sure they are really saved as .asm files and not .asm.txt files!


How the display works

This is a diagram of the way the display and the pixel data are arranged:

a box with the top-left labeled DISPLAY_BASE. the top-left is (0, 0); the top-right is (63, 0); the bottom-left is (0, 63); the bottom-right is (63, 63). The address of the bottom-right is DISPLAY_BASE + 1023.

And this is how the data copying happens:

the RAM pixel data array is on the left. the visible display is on the right. sw zero, DISPLAY_CTRL will copy from the RAM to the display.


1. Drawing the dot on the screen

Do not modify lab4_include.asm. You will not turn it in.

Okay. First let’s make some variables to hold the dot’s position, and draw it based on those variables.

  1. Make two variables in your .data segment (where it says “variables go here!”).
    • Name them dotX and dotY. They are both .words. Initialize them both to 32.
  2. Make a new function called draw_dot. It will have no arguments and return nothing.
    • Remember what all functions must begin and end with?
  3. Call it from your main loop (where it says “code goes here!”).
    • At this point, you can assemble and run your code to make sure nothing has changed. (No news is good news.)
  4. Inside draw_dot, do the following:
    1. Write the equivalent of t0 = DISPLAY_BASE + dotX + dotY * 64.
      • Note: DISPLAY_BASE is a constant and therefore you use li to put it into a register.
      • Or you can do like add t0, t0, DISPLAY_BASE. Up to you.
    2. li t1, COLOR_SOMETHING, where COLOR_SOMETHING is one of the constants in lab4_include.asm.
    3. sb t1, (t0) to put the dot on the screen.

When you assemble and run, you should now get a single dot in the middle of the display! If you don’t, something is wrong, and you should fix it before moving on.

Try this: but then undo/delete these when you’re done trying.


How input works

When the display window is focused (you click on it), you can use the arrow keys (up, down, left, right) and the Z, X, C, B keys to interact with it.

When you load a word from DISPLAY_KEYS, you get a value where each of those eight keys is represented by one bit, where 1 means the key is held down, and 0 means it isn’t.

The KEY_ constants at the top of the include file are there so you don’t have to remember which bit means which key.


2. Moving the dot

  1. Make a new function called check_input. It will have no arguments and return nothing.
  2. Call it from your main loop before the call to draw_dot.
  3. In check_input, do something like this pseudocode:
     lw t0, DISPLAY_KEYS // get the currently-held keys
     t0 = t0 & KEY_R     // mask out the right arrow key
     if(t0 != 0) {
         dotX++          // move right!
     }
    
    • The & operator is bitwise AND, and can be done with the MIPS and instruction.
  4. Now assemble and run.
    • The dot should sit still.
    • Then, click the display window and hit the right arrow key. The dot should move right.
    • It should only move right when you are holding the right arrow key.
  5. Once you’ve got that working, repeat that blob of code three more times with slight changes:
    • KEY_L should decrement dotX
    • KEY_D should increment dotY (yes, increment!)
    • KEY_U should decrement dotY
    • If you copy and paste code, be careful about your label names! You could easily end up with an infinite loop.
  6. Now when you assemble and run:
    • The dot should sit still.
    • It should move in all four directions with the arrow keys.
    • It should move diagonally when you press two arrow keys.
    • And if you go off the top of the screen by holding up… oh no!

3. Moving it around, without crashing

What’s happening is your x and y coordinates are going negative or off the sides of the screen. On the top side, you’re going to start writing into a part of memory you’re not allowed to, so it crashes.

To prevent this, you have to limit the x and y coordinates.

At the end of your check_input function, before it returns, do the equivalent of the following:

dotX = dotX & 63; // bitwise AND again...
dotY = dotY & 63;

It seems like magic, but this is a way to quickly do a modulo by 64. You can only use this trick when you want to modulo by powers of 2.

Now your dot should wrap around to the other side like in the gif at the top of this page. And you’re done!

Note: If you do use the rem instruction to do modulo 64, the program still crashes. This is because rem can give a negative remainder. If that sounds weird, it is, but we’ll talk about it in class. You can use remu instead to get an unsigned remainder.

Try this:


4. More dots!!!

One dot is not enough. Let’s have three. Here’s how it will work:

Now I am going to step back and have you try to solve this on your own. Here are some hints:


Submitting

Do not submit lab4_include.asm.

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.