Due Friday, February 5 at 11:59:59 PM
By the time you have completed this work, you should be able to:
There are two tasks that you need to complete for this week, some with multiple steps. Strictly speaking, you may complete them in any order, though for this lab it is recommended to go in order. The tasks are listed below:
You may use any MIPS instructions or psuedoinstructions you want in order to implement these tasks. However, you will be somewhat restricted on exams. You may want to read the grading policies regarding MIPS assembly instructions for more information.
The initial step below describes how to get the files you will need into the appropriate place. These files are used for all the different tasks.
After you log in, go into your cs64
directory that you created last time:
cd cs64
Then create a new directory for this lab: lab4
:
mkdir lab4
Then go into that directory.
Now copy over all of the files necessary for this week's tasks:
cp ~kyledewey/public_html/cs64/labs/4/lab4funcs.asm ~kyledewey/public_html/cs64/labs/4/swap_array.asm ~kyledewey/public_html/cs64/labs/4/partner.txt .
Note the use of the trailing .
in the above command, which stipulates that the specified files should be copied into the current directory.
This tutorial is a sequence of memory operations, each slightly harder than the last one. In order to allow us to produce different test cases, the main code is separate from your code. We will supply a different main file depending on what we want the initial values to be.
Because it is assembly, it is a little hard to get a high-level sense of what you are being asked to do.
Open the file lab4funcs.asm
.
The first thing to look at is the global variables, declared in the .data
section.
There are four global integers: globalA
, globalB
, globalC
, and globalD
.
Then there is an array: myArray
.
These are the variables you will be working with this week.
Now look at the code in lab4funcs.asm
, particularly the functions (i.e., storevalues
, storeregvalues
, copyvalues
, operations
, arrays
, and arraycalcs
).
There are a number of functions you need to fill in.
We have not gone over how to properly call functions yet, so you might not know how to do all the steps necessary to write a function.
As such, for this portion, you are restricted to use only certain registers (more on why this makes sense later in the class).
Each function uses a different restricted set; for example, with storevalues
, only registers $v0 - $v1
, $t0 - $t7
, and $a0 - $a3
may be used.
Some of the functions receive input; any input received will be in registers $a0 - $a3
.
Additionally, the last line of each function may not be removed (which is always jr $ra
).
Now look at the lines of code in comments.
For each function, there are some lines of code written in high-level language.
Translating those to assembly is your task.
A description of function inputs, along with C-like code describing what the function does, is supplied in the table below.
Like C, semicolon (;
) should be viewed as a sequencing operator, meaning the statements should be performed in order, and previous statements can influence the results of later statements.
For example, with operations
, the second use of globalA
refers to the value of globalA
set in the previous line.
Your code should not trigger a processor-level exception if overflow occurs.
With MIPS, this means that you should use the addu
instruction instead of the add
instruction for adding numbers together.
Function Name | Function Number | Input | Description |
---|---|---|---|
storevalues |
0 |
None |
globalA = 6; globalB = 6; globalC = 30; globalD = 30; |
storeregvalues |
1 |
|
globalA = $a0; globalB = $a1; globalC = $a2; globalD = $a3; |
copyvalues |
2 |
None |
globalA = globalC; globalD = globalB; |
operations |
3 |
None |
globalA = globalB + globalC; globalD = globalA + globalB; |
arrays |
4 |
|
myArray[2] = globalA; globalA = myArray[3]; myArray[0] = $a0; myArray[1] = $a1; |
arraycalcs |
5 |
None |
globalA = myArray[0] + myArray[1]; |
The code is also equipped with a main
function, which calls the functions you will be writing.
Each run of the program calls a single function.
The name of the function to execute is specified with a number, supplied by the user.
The table above specifies which number executes which function (e.g., a user-supplied 1
will call storeregvalues
).
The main
function prints out the global variables and the first four array values before and after every function call, and, depending on the function selected, may also print out registers $a0 - $a3
.
For example, below is a sample run testing storevalues
, which is function number 0
from the preceding table.
User input is shown in bold.
This program loads, stores, and operates on variables Looking at the comments in the code, perform the operations specified. Make sure you make changes to this code in order to test it. You can change the initial values of variables as well as the inputs you test. Global variable values are: 35, 39, 54, 90 Array values are: 537067794, 537002737, 269156513, 7032874 Enter a value: 0 0 Global variable values are: 6, 6, 30, 30 Array values are: 537067794, 537002737, 269156513, 7032874
As an aside, you may have noticed something a bit odd about the memory layout here: we have declared globalA
and other global mutable variables in the .data
section, whereas in class we discussed that global mutable variables should be in the .bss
section.
The reason why is specific to limitations of SPIM; on a real processor, these should be separated out.
Look for the words “TODO”
in the file.
(If you are using vi
, you can search for those words by using “/TODO”
.)
Make sure you test often.
After implementing each function, make sure you run it using the supplied main
function.
Make sure your code does not depend on initial values. Remember that the program we run it on will have different global declarations.
add
/addi
versus addu
/addiu
Recall that add
/addi
will raise a processor-level exception (for the purposes of this class, a fatal error) in the event that the addition sets the overflow bit.
In contrast, addu
/addiu
(where “u
” is short for “unsigned”) will still set the overflow bit, but they will ignore its value.
The behavior of add
/addi
makes sense if we are dealing with signed numbers in two's complement, and the behavior of addu
/addiu
makes sense if we are dealing with unsigned numbers, which do not have a concept of overflow (that is, it's impossible to get a result of an incorrect sign in a context where everything is implicitly either zero or positive).
The above distinction is important when dealing with memory.
Memory addresses are inherently unsigned; there is no such thing as a negative address.
As such, when you manipulate memory addresses (as you would need to do in order to translate myArray[x]
to MIPS assembly), you should be using addu
/addiu
, not add
/addi
.
The signed variants (add
/addi
) will often produce the same result, but if we start dealing with very large addresses, then suddenly the processor might experience an overflow error in a nonsensical context.
It is quite possible, and even common, to need to have both the signed and unsigned variants of add
in the same program.
For example, consider the following snippet of C-like code, where semantically we should provide an error if signed integer overflow occurs:
int* arr = ...; // initialize arr to some valid value int temp = arr[x] + arr[y];
In the above snippet of code, we would need the unsigned variants of add
(that is, addu
/addiu
) in order to grab both arr[x]
and arr[y]
.
However, in order to perform the addition itself (that is, +
), we need to use the signed variant of add
(that is, add
/addi
).
To help clarify this, keep in mind that arr[x]
is shorthand for *(arr + x)
, where arr
is a pointer (it holds a memory address).
As such, arr + x
is adding together items of types int*
(a pointer) and x
(an unsigned integer), which denotes the calculation of a new memory address.
As this is a calculation for a memory address, and memory addresses are unsigned, we use addu
/addiu
.
However, for the addition that takes place in the original code (that is, arr[x] + arr[y]
), this is adding together two values of type int
, a signed integer.
As such, in this case, we are dealing with signed integer arithmetic, hence we use add
/addi
for this addition.
In this task, you will swap values around from within an array. In order to allow us to produce different test cases, the main code is separate from your code. We will supply a different main file depending on what we want the initial values to be.
Open the file swap_array.asm
.
In the .data
section, a number of prompts have been defined, along with an array named myArray
which you will modify the contents of.
In the .text
section, some code for running tests is in place, along with a function stub named doSwap
, which you will define.
As with the previous task, we have not discussed functions yet, and so this might be a little confusing.
The restrictions are that you may only use registers $v0 - $v1
, $t0 - $t7
, and $a0 - $a3
.
Additionally, the last line of each function may not be removed (which is always jr $ra
).
doSwap
Now look at the stub for doSwap
, which contains a snippet of C code in the comments which you must implement.
This snippet is duplicated below for convenience:
unsigned int x = 0; unsigned int y = 8; while (x != 4) { int temp = myArray[x]; myArray[x] = myArray[y]; myArray[y] = temp; x++; y--; }
Your assembly code must do the same thing as the above C code.
Make sure your code does not depend on initial values.
While you may assume that myArray
will always contain exactly 9 elements, you can make no assumptions regarding what values these are.
Remember that the program we run it on will have different global declarations.
In addition, the point made regarding the practical distinction between add
/addi
and addu
/addiu
in the previous task is still relevant for this task.
Correct implementations must utilize both forms, and in the proper manner (that is, addu
/addiu
for memory addresses and other unsigned things, and add
/addi
for signed values).
For testing, the code in main
will ultimately use your doSwap
implementation, and it features a built-in correctness check.
If you would like to test on other array contents, feel free to change the values of myArray
(keeping sure to keep it exactly 9 elements long).
If you do change the values in myArray
, be sure to also change the corresponding values in expectedMyArray
, which holds what the result should be.
When it comes to your implementation, do not simply programmatically copy the values in expectedMyArray
into myArray
; while this will appear to work on your end, on our end the values in expectedMyArray
will not sync up with myArray
as it does in your code, so this would end up always giving the wrong result on our end.
turnin
If you partnered with someone, record the email address they are using for the class in partner.txt
.
For example, if your partner had the email address foo@bar.com, then the contents of partner.txt
should be the following (and only the following):
Partner: foo@bar.com
If you did not partner with anyone, you do not need to (and should not) edit partner.txt
.
Assuming you are in the cs64/lab4
directory you created at the beginning, you can send in your answers via the following command:
turnin lab4@cs64 lab4funcs.asm swap_array.asm partner.txt
You may turn in the same assignment up to 100 times, which is useful if you are working on it incrementally. Note that only the last version you submitted will be graded.
Even if you did not partner with anyone, you should still turn in partner.txt
, which should not have been modified.
Prepared for Computer Science 64 by Diana Franklin, with slight adaptation by Kyle Dewey.