Introduction to Computer Organization (ICO, CCIT 4026) HKU SPACE Community College | AY2526 S2
Problem Overview
Given two vectors C and D:
C = { c0 c1 }
D = { d0 d1 }
Calculate M defined as:
M = Σᵢ₌₀¹ [ dᵢ + int( (cᵢ + dᵢ)² / (dᵢ + 1) ) ]
= d0 + int((c0+d0)² / (d0+1))
+ d1 + int((c1+d1)² / (d1+1))
Using the example SID 12345678 → C = {5, 6}, D = {7, 8}:
M = 7 + int((5+7)² / (7+1)) + 8 + int((6+8)² / (8+1))
= 7 + int(144 / 8) + 8 + int(196 / 9)
= 7 + 18 + 8 + 21
= 54
Program Architecture
The program is made up of three parts:
┌─────────────────────────────────────────────────┐
│ main │
│ Load C, D → $a0-$a3 │
│ jal comp_sum ──────────────────────────┐ │
│ Print $v0 (= M) │ │
└───────────────────────────────────────────│────┘
▼
┌─────────────────────────────────────────────────┐
│ comp_sum($a0=c0, $a1=c1, $a2=d0, $a3=d1) │
│ sum1 = c0 + d0 │
│ jal square_div(sum1, d0) ──────────────┐ │
│ partial1 = d0 + result │ │
│ sum2 = c1 + d1 │ │
│ jal square_div(sum2, d1) ──────────────│──┐ │
│ return partial1 + result2 + d1 │ │ │
└───────────────────────────────────────────│──│─┘
▼ ▼
┌─────────────────────────────────────────────────┐
│ square_div($a0=sum, $a1=d) │
│ return int(sum² / (d+1)) │
└─────────────────────────────────────────────────┘
Register Constraints
| Function | Register | Permission |
|---|---|---|
comp_sum |
$a0–$a3 |
Read AND Write |
comp_sum |
$v0–$v1 |
Read AND Write |
comp_sum |
$sp, $ra |
Stack operation ONLY |
square_div |
$a0–$a1 |
Read ONLY |
square_div |
$s0–$s1 |
Read AND Write |
square_div |
$v0–$v1 |
Read AND Write |
square_div |
$sp, $ra |
Stack operation ONLY |
Calling Convention Rules
| Register Class | Who Saves? | Registers |
|---|---|---|
| Callee-saved | Called function saves/restores | $s0–$s7, $ra (if non-leaf) |
| Caller-saved | Calling function saves/restores | $v0–$v1, $a0–$a3, $t0–$t9 |
comp_sumis a non-leaf function (it callssquare_div) → must save$rain prologue.square_divis a leaf function (calls nothing) → does NOT need to save$ra. It must save$s0and$s1since they are callee-saved.- Parameters cannot be passed via the stack — arguments must go in
$a0–$a3.
Stack Frame Diagrams
comp_sum stack usage
On entry:
┌───────────┐ ← $sp (after prologue, -4)
│ $ra │
└───────────┘
Before 1st call to square_div (additional -12):
┌───────────┐ ← $sp
│ d1=$a3 │ $sp+0
├───────────┤
│ d0=$a2 │ $sp+4
├───────────┤
│ c1=$a1 │ $sp+8
├───────────┤
│ $ra │ $sp+12
└───────────┘
After 1st call restore → back to just [$ra] on stack.
Before 2nd call to square_div (additional -8):
┌───────────┐ ← $sp
│ partial1 │ $sp+0
├───────────┤
│ d1=$a3 │ $sp+4
├───────────┤
│ $ra │ $sp+8
└───────────┘
After 2nd call restore → back to just [$ra] on stack.
On return: pop $ra → stack fully restored.
square_div stack usage
On entry:
┌───────────┐ ← $sp (after prologue, -8)
│ $s1 │ $sp+0
├───────────┤
│ $s0 │ $sp+4
└───────────┘
On return: both restored.
Complete MIPS Assembly Solution
# ============================================================
# ICO Lab 05 — Procedure Call with MIPS
# Calculates M = Σᵢ₌₀¹ [ dᵢ + int((cᵢ+dᵢ)² / (dᵢ+1)) ]
# Using example SID 12345678: C={5,6}, D={7,8}, M=54
# ============================================================
.data
stack: .space 256 # 256 bytes reserved for stack (64 words)
stack_top: # label at high end of stack (TOS)
C: .word 5, 6 # c0=5, c1=6 (derived from SID)
D: .word 7, 8 # d0=7, d1=8 (derived from SID)
msg: .asciiz "M = " # output prefix string
.text
.globl main
# ============================================================
# Function: square_div
# Computes int( sum² / (d+1) )
# Input: $a0 = sum (READ ONLY)
# $a1 = d (READ ONLY)
# Output: $v0 = int(sum² / (d+1))
# Uses: $s0 (square), $s1 (divisor) [callee-saved]
# ============================================================
square_div:
# Step 1.1 — Save callee-saving registers ($s0, $s1)
addi $sp, $sp, -8
sw $s0, 4($sp) # save $s0
sw $s1, 0($sp) # save $s1
# Step 1.2 — Find the square of the first parameter: square = sum * sum
mult $a0, $a0 # HI:LO = sum * sum ($a0 is read-only)
mflo $s0 # $s0 = sum² (lower 32 bits)
# Step 1.3 — Increment second parameter by 1: divisor = d + 1
addi $s1, $a1, 1 # $s1 = d + 1 ($a1 is read-only)
# Step 1.4 — Divide squared result by incremented result
div $s0, $s1 # LO = int($s0 / $s1) = int(sum² / (d+1))
# Step 1.5 — Copy division result to return register
mflo $v0 # $v0 = int(sum² / (d+1))
# Step 1.6 — Restore callee-saving registers
lw $s0, 4($sp) # restore $s0
lw $s1, 0($sp) # restore $s1
addi $sp, $sp, 8
# Step 1.7 — Return to caller
jr $ra
# ============================================================
# Function: comp_sum
# Computes M = d0+int((c0+d0)²/(d0+1)) + d1+int((c1+d1)²/(d1+1))
# Input: $a0 = c0, $a1 = c1, $a2 = d0, $a3 = d1
# Output: $v0 = M
# Uses: $a0-$a3, $v0-$v1 (all allowed read/write)
# $ra saved on stack (non-leaf function)
# ============================================================
comp_sum:
# Step 1.1 — Save callee-saving registers
# comp_sum calls square_div via jal, so $ra will be overwritten.
# Save $ra as callee-saving register for a non-leaf function.
addi $sp, $sp, -4
sw $ra, 0($sp) # save return address
# Step 1.2 — Find sum of first components: sum1 = c0 + d0
add $v0, $a0, $a2 # $v0 = sum1 = c0 + d0
# Step 1.3 — Prepare: sum1 → $a0, d0 → $a1 (done below after saving)
# Step 1.4 — Save caller-saving registers before first call to square_div
# Need to preserve: c1($a1), d0($a2), d1($a3) across the call
addi $sp, $sp, -12
sw $a1, 8($sp) # save c1
sw $a2, 4($sp) # save d0
sw $a3, 0($sp) # save d1
add $a0, $v0, $zero # $a0 = sum1 (arg1 for square_div)
add $a1, $a2, $zero # $a1 = d0 (arg2 for square_div)
# Step 1.5 — Call square_div(sum1, d0)
jal square_div # $v0 = int(sum1² / (d0+1))
# Step 1.6 — Restore caller-saving registers after first call
lw $a1, 8($sp) # restore c1
lw $a2, 4($sp) # restore d0
lw $a3, 0($sp) # restore d1
addi $sp, $sp, 12
# Step 1.7 — Sum first D component with return value: partial1 = d0 + result1
add $v1, $a2, $v0 # $v1 = partial1 = d0 + int(sum1²/(d0+1))
# Step 1.8 — Find sum of second components: sum2 = c1 + d1
add $v0, $a1, $a3 # $v0 = sum2 = c1 + d1
# Step 1.9 — Prepare: sum2 → $a0, d1 → $a1 (done below after saving)
# Step 1.10 — Save caller-saving registers before second call to square_div
# Need to preserve: d1($a3), partial1($v1)
addi $sp, $sp, -8
sw $a3, 4($sp) # save d1
sw $v1, 0($sp) # save partial1
add $a0, $v0, $zero # $a0 = sum2 (arg1 for square_div)
add $a1, $a3, $zero # $a1 = d1 (arg2 for square_div)
# Step 1.11 — Call square_div(sum2, d1)
jal square_div # $v0 = int(sum2² / (d1+1))
# Step 1.12 — Restore caller-saving registers after second call
lw $a3, 4($sp) # restore d1
lw $v1, 0($sp) # restore partial1
addi $sp, $sp, 8
# Step 1.13 — Add the two square_div return values together
add $v0, $v0, $v1 # $v0 = partial1 + int(sum2²/(d1+1))
# Step 1.14 — Add second D component to return register
add $v0, $v0, $a3 # $v0 = M (= partial1 + result2 + d1)
# Step 1.15 — Restore callee-saving registers
lw $ra, 0($sp) # restore return address
addi $sp, $sp, 4
# Step 1.16 — Return to caller
jr $ra
# ============================================================
# Main Program
# ============================================================
__start:
main:
# Step 1.1 — Set up stack pointer to Top of Stack (TOS)
la $sp, stack_top # $sp = high end of stack region
# Step 1.2 — Load all four vector components into argument registers
la $t0, C
lw $a0, 0($t0) # $a0 = c0 = C[0]
lw $a1, 4($t0) # $a1 = c1 = C[1]
la $t0, D
lw $a2, 0($t0) # $a2 = d0 = D[0]
lw $a3, 4($t0) # $a3 = d1 = D[1]
# Step 1.3 — No caller-saving registers need to be preserved in main
# (main does not use any values after the call to comp_sum)
# Step 1.4 — Call comp_sum(c0, c1, d0, d1)
jal comp_sum # $v0 = M
# Step 1.5 — No registers to restore in main
# Step 1.6 — Print result prompt and value
add $t0, $v0, $zero # temporarily save M (syscall will overwrite $v0)
la $a0, msg # load address of "M = "
li $v0, 4 # syscall 4: print string
syscall
add $a0, $t0, $zero # $a0 = M (the computed value)
li $v0, 1 # syscall 1: print integer
syscall
# Step 1.7 — Exit program
li $v0, 10 # syscall 10: exit
syscall
Step-by-Step Execution Trace
Using C = {5, 6}, D = {7, 8}:
In main
| Step | Action | Register State |
|---|---|---|
| Init | Load C, D | $a0=5, $a1=6, $a2=7, $a3=8 |
| 1.4 | jal comp_sum |
enters comp_sum |
In comp_sum (first entry)
| Step | Instruction | Value |
|---|---|---|
| 1.1 | Save $ra on stack |
stack = [$ra] |
| 1.2 | sum1 = c0 + d0 |
$v0 = 5+7 = 12 |
| 1.4 | Save $a1=6, $a2=7, $a3=8; set $a0=12, $a1=7 |
stack = [$ra, 6, 7, 8] |
| 1.5 | jal square_div(12, 7) |
enters square_div |
In square_div (1st call, sum=12, d=7)
| Step | Instruction | Value |
|---|---|---|
| 1.1 | Save $s0, $s1 on stack |
stack grows by 2 |
| 1.2 | square = 12 × 12 = 144 |
$s0 = 144 |
| 1.3 | divisor = 7 + 1 = 8 |
$s1 = 8 |
| 1.4 | div 144 / 8 = 18 |
LO = 18 |
| 1.5 | $v0 = 18 |
return value set |
| 1.6 | Restore $s0, $s1 |
stack restored |
| 1.7 | jr $ra |
returns to comp_sum |
Back in comp_sum
| Step | Instruction | Value |
|---|---|---|
| 1.6 | Restore $a1=6, $a2=7, $a3=8 |
stack = [$ra] |
| 1.7 | partial1 = d0 + result1 = 7 + 18 |
$v1 = 25 |
| 1.8 | sum2 = c1 + d1 = 6 + 8 |
$v0 = 14 |
| 1.10 | Save $a3=8, $v1=25; set $a0=14, $a1=8 |
stack = [$ra, 8, 25] |
| 1.11 | jal square_div(14, 8) |
enters square_div |
In square_div (2nd call, sum=14, d=8)
| Step | Instruction | Value |
|---|---|---|
| 1.1 | Save $s0, $s1 on stack |
stack grows by 2 |
| 1.2 | square = 14 × 14 = 196 |
$s0 = 196 |
| 1.3 | divisor = 8 + 1 = 9 |
$s1 = 9 |
| 1.4 | div 196 / 9 = 21 remainder 7 |
LO = 21 |
| 1.5 | $v0 = 21 |
return value set |
| 1.6 | Restore $s0, $s1 |
stack restored |
| 1.7 | jr $ra |
returns to comp_sum |
Back in comp_sum
| Step | Instruction | Value |
|---|---|---|
| 1.12 | Restore $a3=8, $v1=25 |
stack = [$ra] |
| 1.13 | $v0 = partial1 + result2 = 25 + 21 |
$v0 = 46 |
| 1.14 | $v0 = $v0 + d1 = 46 + 8 |
$v0 = 54 |
| 1.15 | Restore $ra |
stack fully restored |
| 1.16 | jr $ra |
returns to main |
Back in main
- Print
"M = "→ Print54 - Output:
M = 54✓
Key Design Decisions Explained
1. Why save $ra in comp_sum but not in square_div?
comp_sum calls square_div using jal, which overwrites $ra with the return address back to comp_sum. If comp_sum doesn't save its own $ra (pointing back to main) before the jal, that address would be lost and the program could never return to main.
square_div is a leaf function — it makes no jal calls, so $ra is never overwritten while inside it. It safely uses jr $ra at the end without needing to save/restore it.
2. Why save $a0–$a3 before calling square_div?
$a0–$a3 are caller-saved registers. By convention, the called function (square_div) is free to modify them. After square_div returns, comp_sum still needs c1, d0, and d1 to compute partial1 and sum2. Since we cannot guarantee those registers survived the call, comp_sum must save them on the stack beforehand and restore them afterwards.
3. Why save $s0 and $s1 in square_div?
$s0–$s7 are callee-saved registers. The calling function (comp_sum) trusts these registers to be unchanged after any function call. Since square_div uses $s0 and $s1 for its computation (it's not allowed to use $t registers per the constraints), it must save their original values on entry and restore them before returning.
4. How is A[b]*7 computed without mul?
Not applicable here, but in square_div, the multiplication sum × sum uses the MIPS mult instruction (real instruction), not a pseudo-instruction. The result is read from the LO register via mflo.
5. Why use $v1 for partial1 in comp_sum?
The available registers in comp_sum are only $a0–$a3 and $v0–$v1. After the first call to square_div:
- $v0 holds result1 (return value)
- $a0–$a3 are restored to c0, c1, d0, d1
We compute partial1 = d0 + result1 and need to keep it alive across the second call to square_div. Storing it in $v1 (and saving $v1 to the stack before the second call) is the most natural choice given the register constraints.
6. Why is parameters cannot be passed through stack important?
MIPS convention allows up to 4 arguments in $a0–$a3. This lab enforces that rule strictly — both comp_sum and square_div receive all their inputs via argument registers, not via memory locations or the stack. This keeps the interface clean and hardware-efficient.
Expected Output
M = 54
— END OF LAB 05 ANSWERS —