;;;;;;;;;;;;;;
;;; QUESTION 1
;;;;;;;;;;;;;;

== What we are trying to test:

There are 4 big ideas that we want you to have branded into your minds. As far as purely theoretical questions go, this is the first thing we want to ask: what rules do good engineers follow?

== Answer

The main design principles that instruction set programmers abide by are the following (they are heavily stressed in the book at pages 187-188):

Simplicity favors regularity
- fixed size instructions, 3 register operands in every arithmetic operation, keeping the register fields at the same place in every instruction format.

(Smaller is faster)
(- 32 registers only.)

Good design demands good compromises
- Larger addresses and constants in instructions and keeping all instructions the same length.

Make the common case fast
- PC-relative addressing for branches and immediate addressing for constant operands.

== How we graded:

Getting one of the 3 main design principles above in the left box got you 1 point. Getting at least one of the corresponding MIPS instances of that principle got the other point. If you did not get a point for the left box, you could not get one for the right box.

 

;;;;;;;;;;;;;;
;;; QUESTION 2
;;;;;;;;;;;;;;

a)

== What we are trying to test:

Even though two's complement is the defacto standard for integer manipulation, we want you to show you are able to play with a given number of bits for any arbitrary number representation. Here in particular, we actually refer to one's complement and sign-magnitude which, although legacy, correspond to simple/intuitive ideas that were inefficient in hardware.

== Answers

VERSION 0

-3     -127
83     ff
fc      80
fd      81

VERSION 1

-15     -126
8f        fe
f1        82
f0        81

VERSION 2

-127   -5
ff         85
80       fa
81       fb

VERSION 3

-126     -7
fe          87
82         f9
81         f8


== How we graded:

0.5 per box
If you didn't follow the 8bit or hex requirement, then you lost 0.5 for that specific box, but lost at most 1 point if you repeated that mistake across several boxes.



b)

== What we are trying to test:

Even though most of you will remember that for any given number, that number can be represented in any of an infinity of bases, it is usually a pitfall to think that only decimal can use the "decimal point". Here we want to check that you understand that a decimal point is necessary and can be used to represent any fractional part of a number, in any base

== Answers

VERSION 0

-18.25     32.125
-12.4       20.2

VERSION 1

16.25    -64.75
10.4      -40.C

VERSION 2

-33.5      48.125
-21.8      30.2

VERSION 3

64.25      -28.75
40.4        -1c.c

== How we graded:

0.5 per box
You didn't lose points if you forgot the sign or switched the boxes.


;;;;;;;;;;;;;;
;;; QUESTION 3
;;;;;;;;;;;;;;

a)

== What we are trying to test:

We want you to be as knowledgeable with floating point as you are with two's complement. That means knowing what the representations of successive FP numbers are, which requires you to play with both the exponent and mantissa fields (unlike two's complement which really contains one big such field). Also, you need to know to count backwards for negative numbers.

== Answers

VERSION 0

1 11011111 111.....111

VERSION 1

1 11001011 111.....111

VERSION 2

1 10111111 111.....111

VERSION 3

1 11111011 111.....111


== How we graded:

2 points, all or nothing. No partial credit for any mistake.


b)

== What we are trying to test

One big idea behind floating point is that although the range is wider than two's complement, its precision is limited to 24 significanT digits (for single rpecision, 53 for double). That means, as you saw in lab, that sometimes adding a small number and a big one can lead the small one to have no influence on the result. This is part of the concept of rounding, one of the pitfalls of floating point.

== Answer

0 01100111 111.....111

== Explanation

To add two numbers, we first need to make sure they have the same exponent, in order for us to add up their mantissae in an aligned manner. If the smaller number falls beyond the 24th bit of precision of the result, then it will be lost to truncation. Therefore for X+1=1, X must be smaller than 2^(-23), since 2^(-23) would still fall into the last bit of 1's mantissa. So 2^(-24) comes to mind as the answer. However we want the largest number that is smaller than 2^(-23), which is obtained by setting all the mantissa bits to 1 as well, thus getting 2^(-24) + 2^(-25) + 2^(-26) + ... + 2^(-46) + 2^(-47), or the answer above.


== How we graded:

This is more difficult, so we gave 6 points.

Any answer resembling zero, a denormalized number, or a extremely small number got zero (you have no idea of the answer).

You only got credit if your answer or your work showed that you got the idea that the exponent had to be around -24. Once you got that, we gave four points for the right exponent (127 - 24 = 103 = 01100111b), and 2 points for the right mantissa (11....11b).

A mistake encoding the exponent takes out 1 point.
Not biasing the exponent takes out 2 points.
Using -23 or -25 instead of -24 got out another point. Previous rules apply.
The mantissa was graded all or nothing.


;;;;;;;;;;;;;;
;;; QUESTION 4
;;;;;;;;;;;;;;

== What we are trying to test:

Much time has been spent on K&R and being able to manipulate strings and pointers should now be your forte. We wanted to check whether you are confident with pointer arithmetic, as well as able to understand the functionality of a 10-line program quickly enough to make modifications to it.

== Answer

A: OK
B: Buggy
- c2 is not being declared as char *, but as char only
- FIX: char * c1, * c2;
C: Buggy
- string[loc] returns a character... but strlen takes in a POINTER to a character
- FIX: either 'string[loc]', or 'strlen(&string[loc])', or 'strlen(string+loc)', or 'loc < strlen(string)'
D: Buggy
- We want to progress through a substring character by character from our current location (given by loc). string&loc or string|loc doesn't give us a location at all.
- c2 = string+loc
E: Buggy
- We need a test statement in order for the loop to be valid. We want to stop when we get to the end of the substring (ie when the current substring character is '\0')
- FIX: *c1 or c1[0]
F: OK
G: Buggy
- the current code compares POINTERS c1 and c2. Since c1 and c2 are pointing to two different memory locations, chances are this if statement is never going to be true. What we need to do is to compare the characters that they point TO.
- FIX: if(*c1 != *c2) or if(c1[0] != c2[0])
H: Buggy
- The test in the if statement is trivially wrong. We want to return loc only if we successfully reached the end of the substring (ie we matched each character of it). If this is the case, then c1 MUST be pointing at a null character.
- FIX: if(!(*c1))


== How we graded:

Each section was worth 2 points.
"OK" Sections:
All or nothing for selecting the right choide

"BUGGY" Sections:
1 point for the correct selection
0.5 points for correctly identifying the bug.
0.5 points for correctly fixing the code.

Common mistakes:
Forgetting to dereference the c1,c2 pointers.
Many students felt that the bug in C was because strlen() returned an int, not a boolean. In C, any non-0 value is TRUE. Saying 'blah !=0' in a conditional is just the same as saying 'blah'
Many students forgot to add loc to the start of string when fixing D


;;;;;;;;;;;;;;
;;; QUESTION 5
;;;;;;;;;;;;;;

a)

== What we are trying to test:

Translation of C to MIPS without internal function calls, as well as the handling of pointers in assembly. Also testing your knowledge of argument and return value conventions.

== Answer

replaceInt: lw $t0 0($a0)
beq $t0 $0 endLoop #if *array is null, we exit from the for loop
beq $t0 $a1 doReplace #if *array == toReplace, we goto doReplace
addiu $a0 $a0 4 #otherwise we array++
j replaceInt #and reiterate
doReplace: sw $a2 0($a0) #doReplace stores replaceWith into array[0]
move $v0 $a0 #and sets the return value to array
j ret
endLoop: move $v0 $0 #return NULL
ret: jr $ra


== How we graded:

One point off for each line with mistake.
One point off using $s registers.
Multiple points not taken off for same mistakes.



b)

== What we are trying to test:

Function calling conventions, especially expected argument order in $a.. registers.

== Solution:

lw $a0 8($sp) #or la $a0 8($sp) or addi(u) $a0 $sp 8
#note that the first answer actually reads from memory, and is thus a different answer. However the question was vague enolugh to be open to interpretation.
addiu $a1 $0 1 #(or 5, 9, 4, depending on VERSION)
addiu $a2 $0 2 #(or 6, 3, 7)
jal replaceInt

== How we graded

One point for setting up $a0 correctly
One point for setting up $a1 and $a2
One point for using jal to replaceInt


c)

== What we are trying to test:

This is where we evaluate your purely technical MIPS coding skills. Nothing tells us more about that than having you optimize code, since it requires deep knowledge of program flow and of calculated use of instructions.

== Answer

Move line 8 to 0.5 (and change the first branch label (line 1) to ret)
Delete line 7

It was also possible to remove 2 lines, by using $v0 instead of $t0, which would then allow to delete line 8 as well.

== How we graded

Two possible ways:

1) If $t0 was used in the first couple lines, then moving the endloop line to the top of the program would obviate the "j ret" line.

2) If $v0 was used in the first couple lines, then the "j ret" line and "endloop" line can both be deleted.

Common pitfall:
Some people moved beq lines to bne lines, thereby changing the for loop into a do-while loop. This changes the logic of the program. Therefore, it is incorrect.

The grading standard is that producing correct result gets 3 points or otherwise nothing. However, if the answer clearly falls in 1) or 2) and there are minor mistakes, we give discretionary 1 or 2 points.

;;;;;;;;;;;;;;
;;; QUESTION 6
;;;;;;;;;;;;;;

a)

== What we are trying to test:

This one really tests your understanding of MIPS program flow. The concept is that if you understand what a mips program does (no matter if it follows conventions or not) and can reconstruct corresponding code in any high-level language, then it means you have mastered MIPS as a standalone programming language.

== Solution

int mystery(int n) {
if (n == 0)
return 0;
else
return (mystery(n-1) + mystery(n-1) + 1);
}

/* or, optimized */

int mystery(int n) {
if (n == 0)
return 0;
else
return 2*mystery(n-1)+1;
}

/* or, optimized even further */

int mystery(int n) {
return(n ? 2*mystery(n-1)+1 : 0);
}

Side note: The original code was taken from a CS3 example in which we were trying to compute the number of moves for the Tower of Hanoi problem with n disks.


== How we graded

We accepted as full credit any code that effectively did the same thing as the procedure shown above. Many people had an extra n++ at the end (translating the MIPS to C directly not realizing that doesn't do anything).

In terms of dividing up the 10 points...lots of partial credit available.

+2 = base case and correct return value for n==0.
+3 = mystery(n-1)
+4 = second mystery(n-1)
+1 = the last +1 in the return value

-1 for saying "n=0" in your test
-2 for anything extra (i.e., n*mystery(n-1), mystery(mystery(n-1))) -1 for having n-2 instead of n-1 in your second call

== Common errors

Most people 'got it'. Some had (n-2) in their second call. Some thought we were multiplying n*mystery(n-1)...



b)

== What we are trying to test:

As you can see from this MIPS program example, not following conventions can make code quite harder to understand. Also in general cases, it can lead to hard-to-debug, buggy code, especially if the caller and callee code is written by separate programmers. This question really was meant to reveal your understanding of the procedure call register convention. What it served to do was to highlight ANY confusions about this topic quite explicitly. Our feeling was that this was the best question on the test for this reason, and students might expect more short-answer questions in the future as a result...

== Answer

Line 9 violates register conventions, since it assumes that $a0 contains a sensible/valid value upon this (second) jal call. However nothing certifies that the first jal call didn't overwrite $a0 with a different value.

== How we graded

+2 = Saying effectively that (but you could have chosen another line
+number,
e.g., line 7 for NOT saving $a0 before it since it'd be used later,
or line 13 for compensating for this.

Most people who said line 13 didn't talk about the essential problem, which is really line 9's expectations. Most people who chose line 9 had the right reasons.

+1 = Getting really close to the reason but missing a small fact, or
not saying it in a way that showed they really knew it.

Very briefly, the register conventions are:
* Caller sets a0,a1,... to valid argument values before jal calls
* Caller saves volatile registers if they contain information before jal calls
* Callee, if it needs saved registers (which all start with 's'), must save
them before munging on them and then restore before returning
* Callee puts return values in $v0,$v1
* Caller cannot trust non-saved registers (other than $v0-1) upon return
from jal call
* We don't touch or use registers other than the ones we discussed:
$s0-7, $t0-9, $a0-3, $v0-1, $sp, $ra, $0

Most students (the vast majority) received a 0 on this question. Their
answers included:

* #6 - You can't modify the argument $a0

Comment: Completely false. All we know is that it's been set with
our argument. Once we're the callee, we can use it as we like.

* #9 - You have to save $ra again before the second call

Comment: This really shows confusion. $ra is the place we the
subroutine need to return to, so if we are going to do a jal
ourselves (and taken on the role of the callER) then that jal
call will overwrite it. BUT, once we've saved the place we need
to return to, we can make a bazillion jal calls to all kinds of
subroutines and as long as we've saved $ra before the first jal
and restore it before we use it to return with jr $ra, we don't
need to think about it again.

* #11 - It should be addi

Comment: If we're talking TAL, that's true. If we're just looking
at 'MIPS', that assumes MAL, which means we can be sloppy with our
argument types. We know the accepting, graceful assembler will fix
it for us (as well as expand them to lui/ori if the immediate is too
big)

* #10 - We need to restore 0($sp) into $v0 instead of $t0

Comment: We've just made a second jal call, which means there's
a value in $v0. "lw $v0 0($sp)" would clobber that value. There's
nothing that says officially that we need to be symmetric about
the registers we use to store into and load from memory.

We were very pleased to see these misconceptions be revealed so blatantly so that students can correct them early on in the semester...


c) and d)

== What we are trying to test:

Your understanding of recursive functions, and of 32-bit signed integers.

== Answer

Both answers are -1

== Explanation

Most people looked at the pattern of the first few numbers (always a
great place to start) and saw:

n      mystery(n)      mystery(n) in binary
0      0                    0
1      1                    1
2      3                    11
3      7                    111
4      15                  1111
.       ...                    ...
32     2^32-1          11...111

... or basically, mystery(n) fills that many low-order bits with all 1s. Many also saw that this corresponded to the function mystery(n)= 2^n - 1. We can see this works because we always double the previous value (in
effect, shifting the values to the left by one and adding 1, leaving all 1s in the lower bits).

Whether the input to mystery was 32, 33, 34, 35, 36 etc, in all cases you ended up having to fit a very large power of 2 (> 31) -1 into an 32-bit two's complement.

Let's take 32 as an example for the recursive call. The first call to mystery(n) returns 1 + two of mystery(n-1), which in turn return 1 + two of the next lower level, etc. This gives us a pyramid scheme:

32    1
31    1 1
30    1 1 1 1
...
1       1 1 1 1 1 1 1 1 1 1 1 1 1 ... 1 1 1
0       0 0 0 0 0 0 0 0 0 0 0 0 0 ... 0 0 0

Since there are 32 levels of ones, each corresponding to a successive power of 2, that means that the sum of all the ones (and therefore the return value of mystery(n)) is:

2^32 + 2^31 + 2^30 + ... + 2^2 + 2^1 + 2^0
which is 2^33 - 1

Now trying to fit this last number into a 32-bit signed int, we see that we are short of space to actually represent it. As you know, when numbers are too big to be represented after the biggest positive signed integer 2^31 - 1, things wrap around and start over from the negative side. Here there are two tricks to find out which actual number 2^33 - 1 will be displayed as:

mod by 2^32: since there are 2^32 different numbers representable in two's complement 32bit, modding by this number gives us the actual bucket any number will fall into.

(2^33 - 1)%2^32 = 2^33%2^32 - 1%2^32 = -1

2^33 - 1 is really (2 * 2^32 - 1). Since 2^32 is represented as 100000...0000, adding that binary number to itself gets you 0. Then you are left with -1.

You can see that the above strategies are applicable to any of the versions of the exam.

== How we graded

c)

+4 -1
+2 2^32-1 (correct, but didn't recognize that %d => signed int => -1)
+1 Question (a) wrong but did the right arithmetic to get a correct ans The reason we didn't give more points for the correct answer was that it would be possible to have your C code be mystery(int n){return(1)}; and that person would have received credit for putting 1 in both slots. So, in some sense, this is a 'cascading' question, in that you have to get an earlier part right to get the later part right, and in general it's best to avoid cascading questions, but this follow-up to question (a) was so rich we thought it unavoidable.
+0 overflow (remember, C doesn't overflow! And, the MIPS inst that did
the additions were addu's, which themselves don't overflow either.)

d)

+2 -1
+2 2^32-1 (recognizing that this answer would be the same as (c)) because it wraps
+1 2^33-1 (or whatever your version said, replace 33 with 34,35,35)
+0 overflow (remember, C doesn't overflow! And, the MIPS inst that did the additions were addu's, which themselves don't overflow either.)

Meta-note: We saw students' papers filled with calculations. If we say 'no calculators' and you find yourself doing lots of hand-calculations, that's a sign you're going down the wrong path... Just a tip for the future.


;;;;;;;;;;;;;;
;;; QUESTION 7
;;;;;;;;;;;;;;

== What we are trying to test:

This is the only question that tests your knowledge of the R/I/J-formats for MIPS instructions: how to fill the different fields, and how they are represented as machine code.

== Solutions

0x8C83FFFC
0x1060000B (for "10 instructions" before the jump, or 0x1060000D for 12)
0x08020001

== Explanations

Except from simple copying from the textbook to fill in the opcodes, etc., there are a couple pitfalls you should be aware of:
- the jump instruction encodes its jump address as a WORD address, which requires you to divide 0x00080000 by 4, then add 1 for while_loop
- branch instructions encode their target address as a WORD OFFSET *from the next line* (ie count the number of lines from the next until you reach the label)

== How we graded

There are three blank lines. Each line is worth two points.

First Line:

2 points for the correct answer and some work (in binary or decimal) 1 point for an incorrect answer, but you have the list (35,4,3,-4); many people figured out the fields correctly, but then made a mistake translating to hexadecimal
0 points otherwise

Second Line:

2 points for the correct answer and some work (in binary or decimal) 1 point for an incorrect answer, but you have the list (4,3,0,11 or 13) 0 points for an incorrect answer and an incorrect list
NOTE: You get 0 points for an off-by-one error (if you use 10, 12 or 14 (A, C or E) instead of 11 or 13)

Third Line:

2 points for the correct answer
0 points otherwise (no partial credit: it has to be exact)


;;;;;;;;;;;;;;
;;; QUESTION 8
;;;;;;;;;;;;;;

== What we are trying to test:

Instead of having you work on detailed (line-by-line) object file creation (symbol tables, relocations tables, ...) and linking, we wanted to make sure you had the general ideas behind each part of the compiling process, as seen in lecture.

== Solution

ASSEMBLER changes move $t0, $t1 into add $t0, $zero, $t1
LINKER resolves undefined labels using the relocation information and the symbol table
LOADER copies the parameters (if any) to the main program onto the stack

== Howe we graded

1 point each
No partial credit


;;;;;;;;;;;;;;
;;; QUESTION 9
;;;;;;;;;;;;;;

== What we are trying to test:

Abstracting away from details of the implementation of the free list (e.g. buddy system, etc), you get to show you know how specific allocation policies work, and how there are situations where they aren't optimal. First-fit and best-fit are very common policies that you will encounter in a lot of other settings, so they're one thing we really want you to remember from 61c.

== Solution

There are many possible answers to this one, as long as you get Best-fit stuck along the way

Example:

First Best
--A-B --A-B
C-A-B --ACB C=malloc(1)
CDA-B D-ACB D=malloc(1)
CDA-- D-AC- free(B)
CDAEE X E=malloc(2)

== How we graded

Any "no" answer got 0 points.
Any "yes" answer with some justification (better than "I do know it") got 1 point.
Any valid answer, even requiring 11 malloc/free's (yes, there are 11-instruction cases), got full credit.
Mistakes in the algorithms application got 2 points out.