Please read this page about taking my exams!
Exam format
- When/where
- During class, here, like normal
- 75 minutes
- it is not going to be “too long to finish”
- no calculator
- Closed-note
- You may not have any notes, cheat sheets etc. to take the exam
- The open-note thing was just for when we were remote
- Length
- 3 sheets of paper, double-sided
- there are A Number of Questions and I cannot tell you how many because it is not a useful thing to tell you because they are all different kinds and sizes.
- Topic point distribution
- More credit for earlier topics (e.g. memory addresses, arrays, MIPS programming)
- Less credit for more recent ones (e.g. multiplication, division)
- More credit for things I expect you to know because of your experience (labs, project)
- VERY ROUGHLY:
- ~15% multiple choice/answer and fill-in-the-blank
- ~20% short answer (just writing stuff)
- ~20% math (bases, ranges, +/-)
- ~20% MIPS ASM (tracing, interpreting, filling in blanks)
- ~15% understanding memory
- ~10% bitfields
- Kinds of questions
- A few multiple choice and/or “pick n“ (but not many)
- Some fill in the blanks
- mostly for vocabulary
- or things that I want you to be able to recognize, even if you don’t know the details
- Application questions about numbers and arithmetic (i.e. math problems, basically)
- Base conversion
- Interpreting patterns of bits in different ways (signed, unsigned, bitfields, floats etc)
- Unsigned and signed (2’s complement) addition
- Several short answer questions
- again, read that page above about answering short answer questions!!
- No writing code from scratch, but:
- tracing (reading code and saying what it does)
- debugging (spot the mistake)
- interpreting asm as HLL code (identifying common asm patterns)
- fill in the blanks (e.g. picking right registers, right branch instructions)
Things people asked about in the reviews
This is not an exhaustive list of topics on the exam. This is only what people asked about.
- CISC vs RISC
- CISC: Complex Instruction Set Computer
- made for humans to write programs directly in assembly
- RISC: Reduced Instruction Set Computer
- made for compilers to produce assembly/machine code from high-level languages
- The differences are really in the name:
- CISC has complex, flexible, multi-step instructions that are great for humans (do more stuff with fewer instructions!) but terrible for performance
- RISC has reduced, simple, single-step instructions that are great for compilers (so easy to write algorithms to write RISC code!) but more awkward for humans to write
- CISC: Complex Instruction Set Computer
- Accessing arrays in MIPS
- An array is multiple variables of the same type and size, equidistantly spaced apart in memory
- E.g.
arr: .word 1, 2, 3
is 3 words/12 bytes of memory; each item of the array is 4 bytes apart because a word is 4 bytes.
- E.g.
- The address of
A[i]
isA + S×i
where:A
is the address of the array (in asm, the label is the address)S
is the size of one item in bytes (so for.word
it’s 4,.byte
it’s 1, etc)i
is the index you want to access (in asm, typically a register)
- The “long form” of array access looks like:
# ASSUMING that s0 is the index (maybe we're in a for loop and s0 is the loop counter): la t0, arr # t0 = address of arr mul t1, s0, 4 # t1 = s0 * 4 add t0, t0, t1 # t0 = address of arr + (s0 * 4) # Now you can load/store using (t0) as the address lw a0, (t0) # a0 = arr[s0]
- The “short form” folds the
la
andadd
into thelw
instruction, but you still have to multiply the index:
mul t1, s0, 4 # t1 = s0 * 4 lw a0, arr(t1) # a0 = arr[s0]
- The stupid
arr(t1)
syntax means “add the address ofarr
andt1
together, and use that as the address to load from”
- An array is multiple variables of the same type and size, equidistantly spaced apart in memory
- The stack
- A region of memory that contains information about function calls
- Pushing puts a value on top of the stack, popping removes a value from the top of the stack
- A stack is a perfect match for the way function calls work:
- Whenever a function is called, it pushes its activation record (AR) -saved registers, local variables in HLLs
- Whenever a function is about to return, it pops the AR
- ARs are removed in the opposite order from when they are created, so stack is exactly what is needed
- In-progress functions’ data is safe on the stack (in memory)
- Calling Convention
- Honor system used to let multiple functions work together
- Remember that all functions share the registers so this is important!
- Makes them agree on:
- How arguments are passed from caller to callee
- How values are returned from callee to caller
- How control flows from caller to callee, and then back again
- What goes on the stack
- Who is allowed to use which registers, and for what purposes
- Which registers must be preserved across calls, and which can be trashed
- In MIPS, part of this is the
s
register contract- If you want to use some
s
registersx
, you:push sx
at the beginning of the function that wants to use itpop sx
at the end of the function that wants to use it
- By following this protocol, it’s as if every function gets its own
s
registers- But everyone has to follow the protocol, or the guarantee is gone!
- If you want to use some
- Honor system used to let multiple functions work together
- Endianness
- it is a rule which is used to decide the order of BYTES
- when going from things bigger than a byte to bytes
- or vice versa.
- it comes up in…
- memory (cause it’s an array of bytes)
- files (also arrays of bytes)
- networking
- big endian stores the big end (most significant byte) first.
- “read it in order”
0xDEADBEEF
is stored as0xDE, 0xAD, 0xBE, 0xEF
- little endian does the opposite, stores the least significant byte first.
- “swap the order”
0xDEADBEEF
is stored as0xEF, 0xBE, 0xAD, 0xDE
- but 1-byte values and arrays of 1-byte values are immune to endianness
- because they aren’t chopped up
- it is a rule which is used to decide the order of BYTES
- Conversion between hex and binary
- 4 bits = 1 hex digit (nybble)
- The table is simple - count up in binary from
0000
to1111
, and count up in hex from0
toF
next to it. - When going from binary to hexadecimal, group the bits into 4 starting from the right (LSB)
- add 0s to the left side as needed to make a group of 4 bits
- then each group of 4 bits is 1 hex digit
- Conversion from decimal to binary
- I presented one way on the slides, the “long division” method:
- You have to know the binary place values
- From MSB to LSB (left to right):
- If the place value fits into the remainder, put a
1
and subtract it off the remainder - Otherwise put a
0
- If the place value fits into the remainder, put a
- There’s that other method that involves repeatedly dividing by 2 until you get a quotient of 0, and you write every remainder even if it’s a 0, and the binary representation is the remainders read from top to bottom.
- Try doing both methods to see what I mean, if you’re curious.
- I presented one way on the slides, the “long division” method:
- HOW TO DETECT OVERFLOW
- AN OVERFLOW OCCURRED IF:
Addition Subtraction Unsigned MSB carry out is 1 MSB carry out is 0 Signed same sign inputs, different sign output same as addition, but after negating second input - For signed addition: you get an overflow only if you add two numbers of the same sign and get the opposite sign out (e.g. add two positives, get a negative)
- it’s totally possible to add two numbers of the same sign and not have overflow
- also if the inputs are opposite signs, then overflow is impossible.
- Remember that detecting overflow is only the first step.
- Once it has been detected, you can respond to it in 3 ways: store, ignore, fall on the floor (crash)
- in MIPS,
add/sub
crash on signed overflow, andaddu/subu
ignore all overflow. - not all architectures are this limited.
- in MIPS,
- Once it has been detected, you can respond to it in 3 ways: store, ignore, fall on the floor (crash)
- Bitwise AND and its uses
- Two main uses:
- masking: isolating the lowest n bits of a number by ANDing with 2n - 1
- doing fast modulo by 2n by ANDing with 2n - 1
- Notice both of those are the same operation, just different interpretations
- Do not confuse bitwise AND (
&
, works on ints) with logical AND (&&
, works on booleans, is lazy)!
- Two main uses:
- Bitfields
- Given the specification for a bitfield, you can determine these for each field:
- Position: the low bit number (the one on the right)
- this indicates how far to shift left/right for encoding/decoding that field
- Size: high bit + 1 - low bit
- this is how many bits the field is
- Mask:
2^size - 1
- another way of thinking of it is writing
size
1 bits in binary - and then turn that into hex
- e.g. if size = 6, in binary that’s
11 1111
(6 1s in a row) - turn that to hex, it’s
0x3F
- another way of thinking of it is writing
- Position: the low bit number (the one on the right)
- Then, to decode (get a field OUT of an encoded bitfield):
- shift value right by position and AND with mask
- so,
field = (encoded >> FIELD_POSITION) & FIELD_MASK
- e.g. with a position of 7 and a mask of
0x3F
,field = (encoded >> 7) & 0x3F
- Finally, to encode (put fields together into an encoded bitfield):
- shift each field left by position, and or them all together
- e.g. with 3 fields it might look like
encoded = (A << 9) | (B << 7) | (C << 0)
- Given the specification for a bitfield, you can determine these for each field:
- MEMORY
- Alignment
- The address of an n-byte value must be a multiple of n.
- e.g. words are 4 bytes. so, their addresses are multiples of 4 (
0x00, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C,...
)
- e.g. words are 4 bytes. so, their addresses are multiples of 4 (
- There are underlying hardware design reasons for this, but some architectures (like MIPS) will crash your program if you don’t respect this rule.
- The address of an n-byte value must be a multiple of n.
- Zero/sign extension
- when you load a value < 32 bits (byte, half) into a 32-bit register, have to extend it
- want to preserve the same value, just represent it with more bits
lb/lh
does sign extension (copies sign bit (0 OR 1) to left)lbu/lhu
does zero extension (fills extra bits with 0s)- Does not happen with
lw
because you’re loading a 32-bit value into a 32-bit register - same size
- when you load a value < 32 bits (byte, half) into a 32-bit register, have to extend it
- Truncation
- When you store into a half/byte variable, only the least significant bits (rightmost bits) of the register are stored
- The rest are truncated (cut off)
sb
stores the 8 least significant bits of the register in memory and leaves 24 behindsh
stores the 16 least significant bits of the register in memory and leaves 16 behind
- Endianness, but that was discussed above
- Does the CPU crash if you
lw
a byte orlb
a word?- NO! the only thing it will crash for is address misalignment.
- Otherwise it assumes you know what you’re doing and does exactly what you tell it to do.
- Alignment
- The speed of multiplication and division
- Multiplication is made of multiple additions
- Subtraction is made of multiple subtractions
- Addition is commutative and associative, but subtraction is not
- This means that the sub-steps of multiplication can be reordered and even done in parallel
- But the sub-steps of division must always be done in order
- SO:
- the slow, sequential multiplication algorithm is
O(n)
(n = number of bits) - the fast, parallel multiplication algorithm is
O(log n)
- and division is always
O(n)
- yes, even if you guess with the SRT algorithm
- the slow, sequential multiplication algorithm is