Professional Documents
Culture Documents
Csci 260 Study Guide-6
Csci 260 Study Guide-6
Csci 260 Study Guide-6
Calculating the offset. The instruction after the PC is add $s0, $s0, $s1; we start there with an
offset of 0. The next instruction increments makes the offset 1. By the time, we get to the label DONE the
offset is 3.
In short, if you’re branching from a lower address to a higher one, simply start counting with 0 from the
instruction after the PC until you get to the branch target; this will be a positive offset. By the contrary,
if you’re branching from a higher address to a lower one, start counting with -1 right from the PC until
you get to the branch target; this will be a negative offset.
In any case, we’ve determined the offset for the beq $s0, $t0, DONE is 3, so we can now fill out of all
its fields:
opcode rs rt offset
Decimal 4 16 8 3
0x00000008 # PC
0x00000004 # We always add 4
+ 0x0000000C # offset shifted 2 bits left
This results in 0x00000018, i.e., the address of the instruction the label DONE names.
That’s basically what a branch instruction does with the PC and the offset to get to the branch target.
The procedure is generalized as follows:
The 𝑃𝐶 + 4part means the shifted offset is added to the next PC value (i.e., the instruction
immediately after the PC) rather than the PC of the branch itself.
Removing the 4 upper bits and 2 lower bits lets us with the 26 bits for the target
opcode target
Decimal 2 2
How does MIPS find the address of the instruction the jump instruction is jumping to? The j
LOOP instruction is located at memory address 0x00000014 (this is our PC). In order to find out the
memory address LOOP is labeling, MIPS takes the 4 leftmost bits from the PC concatenating them to the
26 bits in its target field, and subsequently shifting left the concatenated bits by 2 (i.e., adding 2
rightmost 0 bits). Thus:
Equals
Which in hexadecimal is 0x00000008, i.e., the memory address the label LOOP names.
That’s basically what a jump instruction does with the PC and the 26-bit target to get to the jump-to
instruction. The procedure is generalized as follows (∼means concatenation):
𝑎𝑑𝑑𝑟𝑒𝑠𝑠 = (𝑃𝐶[31: 28] ∼ 𝑡𝑎𝑟𝑔𝑒𝑡) << 2
If we assume we place the loop starting at location 80000 (in decimal) in memory, what is the MIPS
machine code for this loop?
Answer: Aside from the bne and j instructions, the remaining instructions are just a matter of
identifying their instruction formats and filling out the fields.
Mem. Instruction/label Instruction Format Machine Code (bin) MC (hex)
Loc.
80024 Exit
Getting the imm/address/offset field for the bne and j instructions is as follows:
We obtain the offset by counting how much we take to go from the PC to the destination address. In
this case, we branch off from the instruction at 80012 (our PC) to get to the memory address 80024.
We notice the offset is positive (i.e., moving from a lower address to a higher one), thus we start
counting at 0 from the instruction right after the PC until we get to the expected address. By doing this,
we find out the offset is 2.
● j Loop
The memory address of the instruction we’re jumping to is 80000 (in decimal) and converted to a 32-bit
word is:
This is the target for the address field in the j instruction, which in decimal is 2000.
Summary
Functions/Procedures
A procedure (function, subroutine, etc.) is a programmer’s main way for doing structured programming.
They avoid code repetition and allow for calling functions you didn’t write (e.g., libraries, modules, etc.).
Registers are the fastest place to hold data in memory in a computer, and MIPS software follows the
following convention for procedure calling in allocating its 32 registers:
● Jumps back to the address stored in $ra (put there by the jal instruction).
Let’s start with the following C code fragment. In short, we’ve a function main that calls a function
update with some arguments, whose return value is stored in either variables b or c.
main() {
if (a == 0) int b = update(g, h);
else int c = update(k, m);
}
int update(x, y) {
return (x+y) - (y<<4);
}
main:
bne $s4, $zero, DoElse
jal update # update uses registers $s0, $s1
# Expect results in $s4
j Exit # Done here
DoElse:
jal update # update uses registers $s2, $s3
# Expect results in $s5
Exit:
update:
add $s4, $a0, $a1 # s4 = x + y
sll $s5, $a1, 4 # s5 = y << 4
sub $v0, $s4, $s1 # return v0 = (x + y) - (y << 4)
jr $ra
Question: What is going to happen to the variable a when the procedure main calls the procedure
update?
Answer: Notice that both main and update are using the same register storing a: bne $s4, $zero,
DoElse and add $s4, $a1, $a2. Thus, the variable a will be changed because update changes it.
Thus, if main wants to use it later after the call to update, the value will have changed by then and
that's going to cause some problems.
In fact, that's not the only problem with the above assembly code. The update procedure expects its
arguments in registers $a0 and $a1 but main expects them to be either in registers $s0 and $s1, or
$s2, $s3.
Another problem is that update returns its result in register $v0 but main expects the result in either
register $s4 or register $s5. Similarly, the update procedure uses register $s4 and $s5 to store
temporary values and changes them. However, the main procedure also uses them.
What's the problem overall with this piece of Assembly code? The procedures main() and
update() are incompatible because they don't coordinate how they use the register file. Thus,
they end up overwriting registers with temporary values and potentially losing data. Keep in mind that
all registers are all shared in the register file and as such we need to agree how to share them. In the
end, we need some way to save old values in registers (so we can use the registers for other things),
as well as restoring those old values once we’re done with the registers.
Saving and Restoring Registers to the Call Stack
We need a register convention so callers and callees:
● Know where to expect data (arguments and results).
● Don't overwrite each other's registers.
○ Some registers must be saved by the callee if it uses them.
○ Some registers must be saved by the caller if it uses them.
When saving, we copy a register to memory where they won't be overwritten. And when restoring, we
copy the register back from memory to a register (usually the original one) which restores it back to its
original state.
Why do we need to save and restore anyway? If the caller and the callee don't agree on which
registers they'll be using, they'll end up overwriting each other's data because they don't know what
registers each other is using. This causes multiple problems as discussed in
Procedure Call in Detail: A Case Study .
By saving registers, we’re spilling registers from the CPU into the memory and it was decided that the
ideal data structure to handle this is the stack. A MIPS call stack (or simply the stack) is a special
piece of memory where we can save registers. It works the same as a regular stack data structure:
● add data by pushing it into the stack, and
● remove data by popping it from the stack.
TRIVIA: You’ve probably come across the term call stack in HLL programming texts when discussing
recursion.
The stack pointer ($sp, register 29) keeps the address of the end of the stack, and for each register
that is saved or restored from the stack the stack pointer is adjusted by one word. By historical
precedent, MIPS stacks “grow” down (i.e., from higher addresses to lower addresses). Thus, space for
values to be pushed onto the stack is created by subtracting from the stack pointer and space
for values to be popped from the stack is restored by adding to the stack pointer. Quite the
mouthful.