---
# System prepended metadata

title: 'Assignment1: RISC-V Assembly and Instruction Pipeline'
tags: [RISC-V, Computer Architecture]

---

# Assignment1: RISC-V Assembly and Instruction Pipeline
contributed by <`SuNsHiNe-75`>

## [Quiz 1](https://hackmd.io/@sysprog/arch2024-quiz1-sol) - Problem C

:::danger
Choose one problem (A, B, or C) from [Quiz1](https://hackmd.io/@sysprog/arch2024-quiz1-sol), translate it from C code to a complete RISC-V assembly program, and include the relevant test data.
:::

### C Code
The C code for the `__builtin_clz` function mentioned in Problem C is as follows:
```c
static inline int my_clz(uint32_t x) {
    int count = 0;
    for (int i = 31; i >= 0; --i) {
        if (x & (1U << i))
            break;
        count++;
    }
    return count;
}
```

:::danger
Show the full C source code corresponding to the original problem set.
:::


### RISC-V Assembly Code
```c
    .text
    .globl main
    
main:
    li a0, 1
    call my_clz
    
    li a7, 10
    ecall

my_clz:
    li t0, 0
    li t1, 31

loop:
    li t2, 1
    sll t2, t2, t1
    and t3, a0, t2
    bnez t3, done
    addi t0, t0, 1
    addi t1, t1, -1
    blt t1, zero, done
    j loop

done:
    mv a0, t0
    ret
```

:::danger
You should automate the testing procedures as much as possible, meaning there's no need to manually check each program output. Instead, implement internal validation to handle the result checking.
:::

A brief analysis of registers of the above assembly code:
- `a0` can be used to specify the input parameter `x`.
- `t0` represents the `count`.
- `t1` serves as the loop index `i`.
- `t3` is used to detect whether the currently observed bit is `1`. If it is, the calculation is completed, and the loop is exited.

The program logic **calculates the leading zeroes of `x`** (`a0`) and stores the result back in `a0`, then ends program execution using `li a7, 10` + `ecall`.

:::info
It is important to note that the built-in C function `__builtin_clz` results in undefined behavior when testing a value of 0. Therefore, in this assembly program, the input value cannot be 0.
:::

### RISC-V Assembly Code Improvement
```c
    .text
    .globl main

main:
    li a0, 1
    call my_clz

    li a7, 10
    ecall

my_clz:
    li t0, 0
    li t1, 31

    li t2, 0xFFFF0000
    srli t3, a0, 16
    beqz t3, L1
    li t4, 16
    sub t1, t1, t4
    mv a0, t3
L1:
    li t2, 0xFF00
    srli t3, a0, 8
    beqz t3, L2
    li t4, 8
    sub t1, t1, t4
    mv a0, t3
L2:
    li t2, 0xF0
    srli t3, a0, 4
    beqz t3, L3
    li t4, 4
    sub t1, t1, t4
    mv a0, t3
L3:
    li t2, 0xC
    srli t3, a0, 2
    beqz t3, L4
    li t4, 2
    sub t1, t1, t4
    mv a0, t3
L4:
    li t2, 0x2
    srli t3, a0, 1
    beqz t3, L5
    li t4, 1
    sub t1, t1, t4
L5:
    mv a0, t1
    ret
```

This assembly program uses **bitwise operations** to avoid the loop execution of `my_clz`.

| Assembly program    | Cycles | Instrs. retired | IPC   | CPI  |
| ------------------- | ------ | --------------- | ----- | ---- |
| `my_clz`            | 335    | 261             | 0.78  | 1.28 |
| branchless `my_clz` | 45     | 25              | 0.556 | 1.8  |

In the same test scenario of "calculating the number of leading zeroes for 1," we can observed the **Execution Info.** simulated in *Ripes* and organized the table as shown above.

It is clear that the cycle count for the branchless version of `my_clz` is only $\frac 1 7$ of that for the original version with the `for` loop logic `my_clz`.

However, when examining the CPI and other indicators, the overall execution performance of the branchless `my_clz` is slightly inferior to that of the original `my_clz`, indicating that the performance bottleneck needs to be clarified.

:::danger
You should automate the testing procedures as much as possible, meaning there's no need to manually check each program output. Instead, implement internal validation to handle the result checking.
:::

## Use Case

### LeetCode: [Factorial Trailing Zeroes](https://leetcode.com/problems/factorial-trailing-zeroes/description/)

> Given an integer `n`, return the number of trailing zeroes in `n!`.

### C Code
```c
int trailingZeroes(int n) {
    int ans = 0;
    while (n) {
        ans += n / 5;
        n /= 5;
    }
    return ans;
}
```

### RISC-V Assembly Code
```c
    .text
    .globl trailingZeroes

trailingZeroes:
    addi    sp, sp, -16
    sw      ra, 12(sp)
    sw      s0, 8(sp)
    sw      a0, 4(sp)

    li      s0, 0
    lw      t0, 4(sp)

loop:
    beq     t0, zero, end
    li      t2, 5
    div     t1, t0, t2
    add     s0, s0, t1
    mv      t0, t1
    j       loop

end:
    mv      a0, s0
    lw      ra, 12(sp)
    lw      s0, 8(sp)
    addi    sp, sp, 16
    li      a7, 10
    ecall
    jr      ra
```