Csci 260 Study Guide-6

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 10

Addr Label Instruction Comment

0x00000000 add $s1, $zero, $zero #


0x00000004 addi $t0, $zero, 10 #
0x00000008 LOOP: beq $s0, $t0, DONE # ⇐ current PC
0x0000000C add $s0, $s0, $s1 # 0
0x00000010 addi $s1, $s1, 1 # 1
0x00000014 j LOOP # 2
0x00000018 DONE: # 3 ⇐ Target

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

Binary 000100 10000 01000 0000 0000 0000 0011


How does MIPS find the address of the label the branch instruction is branching to? The memory
address of the PC, namely beq $s0, $t0, DONE, is 0x00000008. In order to find out the address of
the instruction DONE is the label of, we take the PC, add 4, and finally add the offset shifted left by 2 bits.
Thus,

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:

𝑏𝑟𝑎𝑛𝑐ℎ 𝑡𝑎𝑟𝑔𝑒𝑡 = (𝑃𝐶 + 4) + (𝑜𝑓𝑓𝑠𝑒𝑡 << 2)

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.

Graphically the example can be depicted as follows above:


Jumps
Calculating the target. For a jump instruction, the only thing we need to calculate is the 26-bits target.
To calculate the target we simply take the address of the label j is jumping to and remove both the 4
upper bits and 2 lower bits. The address of the LOOP label is 0x00000008. In binary:

0000 0000 0000 0000 0000 0000 0000 1000

Removing the 4 upper bits and 2 lower bits lets us with the 26 bits for the target

0000 0000 0000 0000 0000 0000 10

Thus, 2 is the target in decimal.

Filling out the fields for the j instruction results in

opcode target

Decimal 2 2

Binary 000010 00000000000000000000000010

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:

(0000 ~ 00 0000 0000 0000 0000 0000 0010) << 2

Equals

0000 0000 0000 0000 0000 0000 0000 1000

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

Graphically the example above can be depicted as follows:

Example: To the Point


Take the following MIPS assembly code (pg. 115) for a while loop (pg. 92-93):

Loop: sll $t1, $s3, 2 # Temp reg $t1 = 4 * i


add $t1, $t1, $s6 # $t1 = address of save[i]
lw $t0, 0($t1) # Temp reg $t0 = save[i]
bne $t0, $s5, Exit # go to Exit if save[i] ≠ k
addi $s3, $s3, 1 # i = i + 1
j Loop # go to Loop
Exit:

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.

80000 sll $t1, $s3, 2 0000 0000 0001 0011 0x00138880


1000 1000 1000 0000
0 0 19 9 2 0

80004 add $t1, $t1, 0000 0010 0011 0110 0x02368800


$s6 1000 1000 0000 0000
0 9 22 9 0 32

80008 lw $t0, 0($t1) 1000 1110 0010 1000 0x8E280000


0000 0000 0000 0000
35 9 8 0

80012 bne $t0, $s5, 0010 0101 0001 0101 0x25150000


Exit 0000 0000 0000 0010
5 8 21 2

80016 addi $s3, $s3, 0010 0010 0111 0011 0x22730001


1 0000 0000 0000 0001
8 19 19 1

80020 j Loop 0000 1000 0000 0000 0x08004E20


0100 1110 0010 0000
2 2000

80024 Exit

Getting the imm/address/offset field for the bne and j instructions is as follows:

● bne $t0, $s5, Exit

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:

0000 0000 0000 0001 0011 1000 1000 0000

Removing the 4 leftmost and 2 rightmost bits results in

0000 0000 0001 0011 1000 1000 00

This is the target for the address field in the j instruction, which in decimal is 2000.

Summary

How to Assemble a Single Instruction


To assemble a single MIPS instruction into machine code:
1. Decide which instruction format it is (R, I, J).
2. Determine the value of each component using a MIPS reference sheet. For branch instructions,
you must also calculate the offset and for jump instructions, you must calculate the branch
target.
3. Convert to binary if needed.
4. Convert the 32-bit word to hexadecimal.

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.).

In the execution of a procedure, a program must follow these six steps:


1. Put parameters in place where the procedure can access them.
2. Transfer control to the procedure.
3. Acquire the storage resources needed for the procedure.
4. Perform the desired task described in the procedure's body.
5. Put the result value in a place where the calling program can access it.
6. Return control to the point of origin (i.e., where the procedure was called from), since a
procedure can be called from several points in a program.

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:

● $a0-$a3: four argument registers in which to pass parameters to the function


● $v0-$v1: two value registers in which to store returned values. Why two though?
● $ra: one address register in which to store the point of origin's address. Although the register
$ra isn’t addressable and only accessible by the jal instruction.

Doing a Procedure Call


A caller is the procedure that calls the callee (i.e., the procedure being called). Whenever the caller
calls the callee, the caller places the arguments (i.e., data) that the callee needs in some registers that
the callee can access. Then, the callee returns the result from operating on that data to the caller by
placing it into some register accessible by the caller.

In short, to do the procedure call we need the following two things:


1. Transfer control to the calle to start the procedure:
● The caller program puts the parameter values in the register $a0-$a3 and uses the jal
(jump-and-link) instruction to jump into the procedure and store the address of the
instruction immediately after the procedure call.

jal ProcedureAddress # jump-and-link to the procedure's address


● This keeps track of the instruction right after the jal instruction so we can continue in the
right place when we are done with the procedure.
● Stores the return address (PC + 4) in the $ra (register 31). PC, which stands for
program counter, is an implicit/hidden register that stores the address of the current
instruction being executed. In other words, jal simply stores the address of the next
instruction. This makes sense because if jal stored the PC then it’d create an infinite
loop and the next instruction would never execute.
2. Return control to the caller when the procedure is done:
● Before returning control to the caller, the callee then performs the calculations, places
the results into the registers $v0-$v1, and returns control to the caller using the jr (jump
register) instruction.
jr $ra # jump-return to the address in $ra

● Jumps back to the address stored in $ra (put there by the jal instruction).

Procedure Call in Detail: A Case Study


This section is mainly to showcase some of the issues that arise if we don’t use registers carefully and
don’t come up with a convention that prevents pieces of code from stepping on other code’s data. Many
times you’ll ask yourself why you must follow a register convention, and this section is the answer to
that.

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);
}

The MIPS assembly code is shown down below.

# Allocate procedure main's registers


# $s4 = a, $s5 = b, $s6 = c
# $s0 = g, $s1 = h, $s2 = k, $s3 = m

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:

# Allocate procedure update's registers


# Arguments: $a1 = x, $a2 = y
# $s4 = x + y, $s5 = y << 4

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.

You might also like