---
# System prepended metadata

title: arch2025-homework1

---

# Assignment 1: RISC-V Assembly and Instruction Pipeline
contributed by < [xwt](@xwt) >
> **AI tools usage**


## Problem b in [Quiz1](https://hackmd.io/@sysprog/arch2025-quiz1-sol)

### Overview
Assume uf8 implements a logarithmic 8-bit codec that maps 20-bit unsigned integers (
[0,1,015,792]) to 8-bit symbols via logarithmic quantization, delivering 2.5:1 compression and ≤6.25% relative error.

#### Decoding
$$　D(b) = m \cdot 2^e + (2^e - 1) \cdot 16　$$ 

#### Encoding
$$　E(v) = \begin{cases}
v, & \text{if } v < 16 \\
16e + \lfloor(v - \text{offset}(e))/2^e\rfloor, & \text{otherwise}
\end{cases}　$$
Where  $\text{offset}(e) = (2^e - 1) \cdot 16　$

#### Error Analysis
Absolute Error: $\Delta_{\max} = 2^e - 1$
Relative Error: $\varepsilon_{\max} = 1/16 = 6.25\%$
Expected Error: $\mathbb{E}[\varepsilon] \approx 3\%$

#### Information Theory
Input Entropy: 20 bits
Output Entropy: 8 bits
Theoretical Minimum: 7.6 bits (for 6.25% error bound)
Efficiency: 8/7.6 = 95% optimal

| Exponent | Range                | Step Size |
| -------- |:-------------------- | --------- |
| 0        | [0, 15]              | 1         |
| 1        | [16, 46]             | 2         |
| 2        | [48, 108]            | 4         |
| 3        | [112, 232]           | 8         |
| ...      | ...                  | $2^e$     |
| 15       | [524,272, 1,015,792] | 32,768    |

#### clz
A key part of the uf8_encode process is the use of the CLZ (Count Leading Zeros) operation. CLZ quickly determines the position of the most significant bit in a 32-bit integer. This allows the encoder to efficiently estimate the exponent that represents the magnitude of the input value. Instead of using slow logarithmic or floating-point operations, clz provides a fast integer-based way to identify how large a number is.
1. clz stackless 
Uses conditional branches (beq /bne) to progressively shift and test bits. It shifts the number right by 16, 8, 4, 2, 1 bits iteratively, halving c each time, to find where the first 1 appears.

2.  clz branchless
The code counts leading zeros (CLZ) of a 32-bit unsigned value without any branch that depends on the data.
It performs a fixed sequence of “probe & shift” on chunk sizes 16, 8, 4, 2, 1 bits:

| Feature                  | clz_stackless      | clz_branchless                            |
|:------------------------ | ------------------ |:----------------------------------------- |
| **Uses branches**        | Yes                | No                                        |
| **Timing behavior**      | Variable           | Constant                                  |
| **Code size**            | Smaller            | Larger                                    |
| **Impact**               | Pipeline may flush | No flush                                  |
| **Applicable Scenarios** | low-end MCUs       | CPUs with pipelines such as RISC-V or ARM |

* Pipeline flush
A pipeline flush occurs when the instructions currently in the CPU pipeline must be discarded because the processor’s control flow has changed unexpectedly, usually due to a branch misprediction, exception, or interrupt. When this happens, all the instructions that were fetched or partially executed after the mispredicted branch (or before the correct target is known) become invalid, so the processor flushes them out of the pipeline and starts fetching the correct instructions again. This process causes the CPU to discard partially executed instructions, refill the pipeline from the correct instruction address, and results in a performance penalty due to several lost cycles. Pipeline flushes are commonly caused by branch mispredictions, exceptions, interrupts, self-modifying code, or pipeline hazards that invalidate prior assumptions. They lead to reduced instruction throughput and decreased efficiency, especially in deep pipelines such as modern RISC-V or ARM processors.

To avoid a pipeline flush, I implemented the clz_branchless version.

### clz
#### stackless
The code provided by the professor is a stackless version. I first implemented this version, and then compared it with the branchless version.

* c code
```c=
static inline unsigned clz(uint32_t x)
{
    int n = 32, c = 16;
    do {
        uint32_t y = x >> c;
        if (y) {
            n -= c;
            x = y;
        }
        c >>= 1;
    } while (c);
    return n - x;
}
```

* assebly code
```risc-v=
clz:
    li    t0, 32          # n = 32
    li    t1, 16          # c = 16
c:  
    srl   t2, a0, t1      # y = x >> c
    beqz  t2, y          # if (y == 0) skip
    sub   t0, t0, t1      # n -= c
    mv    a0, t2          # x = y
y:  
    srli  t1, t1, 1       # c >>= 1
    bnez  t1, c          # while (c)
    sub   a0, t0, a0      # return n - x
    ret
```
![螢幕擷取畫面 2025-10-19 183333](https://hackmd.io/_uploads/rkxj04zCgg.png)

#### branchless
```risc-v=
clz_branchless:
    li      t0, 32          # n = 32
    mv      t1, a0          # u = x

    # step 16
    srli    t2, t1, 16      # t2 = u >> 16
    snez    t2, t2          # t2 = (u>>16)!=0 ? 1:0
    slli    t3, t2, 4       # t3 = t2<<4 = 0 or 16
    srl     t1, t1, t3      # u >>= t3
    sub     t0, t0, t3      # n -= t3

    # step 8
    srli    t2, t1, 8
    snez    t2, t2          # 0/1
    slli    t3, t2, 3       # 0 or 8
    srl     t1, t1, t3
    sub     t0, t0, t3

    # step 4
    srli    t2, t1, 4
    snez    t2, t2          # 0/1
    slli    t3, t2, 2       # 0 or 4
    srl     t1, t1, t3
    sub     t0, t0, t3

    # step 2
    srli    t2, t1, 2
    snez    t2, t2          # 0/1
    slli    t3, t2, 1       # 0 or 2
    srl     t1, t1, t3
    sub     t0, t0, t3

    # step 1
    srli    t2, t1, 1
    snez    t2, t2          # 0/1
    mv      t3, t2          # 0 or 1
    srl     t1, t1, t3
    sub     t0, t0, t3

    # return n - u
    sub     a0, t0, t1
    ret
```
![螢幕擷取畫面 2025-10-19 183949](https://hackmd.io/_uploads/S1grMeBf0ll.png)

In the Ripes simulator, the execution results show that the stackless version required 56 cycles to complete 28 instructions, giving a CPI of 2.0 and IPC of 0.5. In contrast, the branchless version finished in only 43 cycles while executing 33 instructions, achieving a CPI of 1.3 and IPC of 0.767.
This means the branchless implementation, though slightly longer in instruction count, executed more efficiently because it avoided conditional branches that often cause pipeline flushes. Without these branches, the instruction flow was smoother, minimizing stalls and allowing better pipeline utilization.
As a result, the branchless version improved performance by reducing wasted cycles, increasing instruction throughput, and achieving a higher execution efficiency — an expected outcome on pipelined architectures like RISC-V.

### uf8_decode
``` risc-v=
uf8_decode:  
    andi   t0, a0, 0x0F          # t0 = mantissa
    srli   t1, a0, 4             # t1 = exponent (0..15)
    li     t2, 15
    sub    t2, t2, t1            # t2 = 15 - exponent
    li     t3, 0x7FFF
    srl    t3, t3, t2            # t3 = 0x7FFF >> (15 - exponent)
    slli   t3, t3, 4             # t3 <<= 4 = offset
    sll    t4, t0, t1            # t4 = mantissa << exponent
    add    a0, t4, t3            # a0 = t4 + offset
    ret
```

### uf8_encode
``` risc-v=
uf8_encode:
    addi sp, sp, -4          # Allocate stack space# Allocate stack space
    sw ra, 0(sp)            # Save return address of test  
    mv s3, a0
    li t0, 16
    blt  a0, t0, encode_done
    jal   ra, clz             # a0 = lz
    li    t1, 31
    sub   t1, t1, a0          # t1 = msb
    li    t2, 0               # exponent
    li    t3, 0               # overflow
    li    t4, 5 
    blt t1, t4, exact            # t4=(msb<5)
    addi  t2, t1, -4          # exponent = msb-4
    li    t4, 16
    blt t2, t4 , overflow    #exponent < 16
    li    t2, 15         #exponent = 15
overflow:
    li    t5, 0               # e=0
overflow_loop:
    bge  t5, t2, estimate   #e >= exponent
    slli  t3, t3, 1    #overflow << 1
    addi  t3, t3, 16   #(overflow << 1) + 16
    addi  t5, t5, 1   #e++
    j overflow_loop
estimate:
    beqz  t2, exact #exponent = 0
    bge  s3, t3, exact #value < overflow
    addi  t3, t3, -16  #overflow - 16
    srli  t3, t3, 1  #(overflow - 16) >> 1
    addi  t2, t2, -1  # exponent--
    j estimate
exact:    
    li t5, 15
    bgeu  t2, t5, exact_done     # if (exponent >= 15) break
    slli  t4, t3, 1    #(overflow << 1)
    addi  t4, t4, 16    #next_overflow=(overflow << 1) + 16
    bltu  s3, t4, exact_done  ## value < next_overflow break
    mv    t3, t4        #overflow = next_overflo
    addi  t2, t2, 1        #exponent++
    j exact
exact_done:
    sub   t4, s3, t3    #value - overflow
    srl   t4, t4, t2    #(value - overflow) >> exponent
    slli  t2, t2, 4    #exponent << 4
    or    a0, t2, t4    #(exponent << 4) | mantissa
encode_done:
    lw    ra, 0(sp)
    addi  sp, sp, 4
    ret

```

### Result
![螢幕擷取畫面 2025-11-06 214444](https://hackmd.io/_uploads/ByIRIQcJbx.png)

![螢幕擷取畫面 2025-11-06 214425](https://hackmd.io/_uploads/HJggPX9J-x.png)

![螢幕擷取畫面 2025-11-06 225204](https://hackmd.io/_uploads/ryyKL451-x.png)

![螢幕擷取畫面 2025-11-06 225224](https://hackmd.io/_uploads/BJ4tU45kbg.png)


### Problem b full code

:::spoiler full c code
```c=
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

typedef uint8_t uf8;

static inline unsigned clz(uint32_t x)
{
    int n = 32, c = 16;
    do {
        uint32_t y = x >> c;
        if (y) {
            n -= c;
            x = y;
        }
        c >>= 1;
    } while (c);
    return n - x;
}

/* Decode uf8 to uint32_t */
uint32_t uf8_decode(uf8 fl)
{
    uint32_t mantissa = fl & 0x0f;
    uint8_t exponent = fl >> 4;
    uint32_t offset = (0x7FFF >> (15 - exponent)) << 4;
    return (mantissa << exponent) + offset;
}

/* Encode uint32_t to uf8 */
uf8 uf8_encode(uint32_t value)
{
    /* Use CLZ for fast exponent calculation */
    if (value < 16)
        return value;

    /* Find appropriate exponent using CLZ hint */
    int lz = clz(value);
    int msb = 31 - lz;

    /* Start from a good initial guess */
    uint8_t exponent = 0;
    uint32_t overflow = 0;

    if (msb >= 5) {
        /* Estimate exponent - the formula is empirical */
        exponent = msb - 4;
        if (exponent > 15)
            exponent = 15;

        /* Calculate overflow for estimated exponent */
        for (uint8_t e = 0; e < exponent; e++)
            overflow = (overflow << 1) + 16;

        /* Adjust if estimate was off */
        while (exponent > 0 && value < overflow) {
            overflow = (overflow - 16) >> 1;
            exponent--;
        }
    }

    /* Find exact exponent */
    while (exponent < 15) {
        uint32_t next_overflow = (overflow << 1) + 16;
        if (value < next_overflow)
            break;
        overflow = next_overflow;
        exponent++;
    }

    uint8_t mantissa = (value - overflow) >> exponent;
    return (exponent << 4) | mantissa;
}

/* Test encode/decode round-trip */
static bool test(void)
{
    int32_t previous_value = -1;
    bool passed = true;

    for (int i = 0; i < 256; i++) {
        uint8_t fl = i;
        int32_t value = uf8_decode(fl);
        uint8_t fl2 = uf8_encode(value);

        if (fl != fl2) {
            printf("%02x: produces value %d but encodes back to %02x\n", fl,
                   value, fl2);
            passed = false;
        }

        if (value <= previous_value) {
            printf("%02x: value %d <= previous_value %d\n", fl, value,
                   previous_value);
            passed = false;
        }

        previous_value = value;
    }

    return passed;
}

int main(void)
{
    if (test()) {
        printf("All tests passed.\n");
        return 0;
    }
    return 1;
}
```
:::

:::spoiler full assembly code
```risc-v=
.data
str0:  .string ":produces value "
str1:  .string " but encodes back to "
str2:  .string ":value "
str3:  .string " <= previous_value "
str4:  .string "\n All tests passed.\n"
str5:  .string "\n somthing wrong.\n"
nl:    .string "\n"

.text

.globl main
#
main:
    addi  sp, sp, -4        # adjust stack for item
    sw    ra, 0(sp)         # save return address of main
    jal   ra, test          # go to test
    beqz  a0, fail          # tests failed
    la    a0, str4          # load string4
    li    a7, 4             # Call system call (printString)
    ecall
    j     exit
fail:
    la    a0, str5          # load string4
    li    a7, 4             # Call system call (printString)
    ecall
exit:
    lw    ra, 0(sp)         # restore register for caller
    addi  sp, sp, 4         # adjust stack to delete item
    li    a7, 10            # exit the program
    ecall

test:
    addi  sp, sp, -20       # allocate stack space
    sw    ra, 16(sp)        # save return address of test
    sw    s0, 12(sp)        # save s0
    sw    s1, 8(sp)         # save s1
    sw    s1, 4(sp)         # save s2
    sw    s1, 0(sp)         # save s3
    li    s0, -1            # previous_value = -1
    li    s1, 1             # bool passed = true
    li    s2, 0             # i = 0
    li    s3, 0             # value = 0
loop1:
    li    t0, 256           # t0 = 256
    bge   s2, t0, true      # i = 256 go to true
    mv    a0, s2            # a0 = i
    jal   ra, uf8_decode    # call uf8_decode
    mv    s3, a0            # s3 = value = uf8_decode(fl)
    jal   ra, uf8_encode    # call uf8_encode
    mv    t2, a0            # t2 = fl2 = uf8_encode(value)
    beq   s2, t2, next      # fl = fl2 go to next
    li    s1, 0             # bool passed = 0 (False)
    mv    a0, s2            # fl
    jal   ra, print_int     # print int (fl)
    la    a0, str0          # ":produces value "
    jal   ra, print_str     # print string (str0)
    mv    a0, s3            # value
    jal   ra, print_int     # print int (value)
    la    a0, str1          # "but encodes back to "
    jal   ra, print_str     # print string (str1)
    mv    a0, t2            # fl2
    jal   ra, print_int     # print int (fl2)
    jal   ra, print_nl      # print string (nl)

next:
    blt   s0, s3, update    # previous < value go to update
    li    s1, 0             # bool passed = 0 (False)
    mv    a0, s2            # fl
    jal   ra, print_int
    la    a0, str2          # ":value "
    jal   ra, print_str
    mv    a0, s3            # value
    jal   ra, print_int
    la    a0, str3          # "<= previous_value"
    jal   ra, print_str
    mv    a0, s0            # previous_value
    jal   ra, print_int
    la    a0, str3
    jal   ra, print_nl
update:
    mv    s0, s3            # previous_value = value
    addi  s2, s2, 1         # i++
    j     loop1             #go to loop1
true:
    mv    a0, s1            # move bool value into a0
    lw    s3, 0(sp)         # restore s3-0 from stack
    lw    s2, 4(sp)
    lw    s1, 8(sp)
    lw    s0, 12(sp)
    lw    ra, 16(sp)        # restore ra from stack
    addi  sp, sp, 20        # release this function's stack frame
    ret                     # return to caller

uf8_decode:
    andi  t0, a0, 0x0F      # mantissa = fl & 0x0f
    srli  t1, a0, 4         # exponent = fl >> 4
    li    t2, 15
    sub   t2, t2, t1        # t2 = 15 - exponent
    li    t3, 0x7FFF
    srl   t3, t3, t2        # t3 = 0x7FFF >> (15 - exponent)
    slli  t3, t3, 4         # offset= t3 <<= 4
    sll   t4, t0, t1        # t4 = mantissa << exponent
    add   a0, t4, t3        # a0 = t4 + offset
    ret                     # return to caller

uf8_encode:
    addi  sp, sp, -4
    sw    ra, 0(sp)
    li    t0, 16
    blt   a0, t0, encode_done #value < 16 go to encode_done
    jal   ra, clz             # call clz,return a0(lz)
    li    t1, 31
    sub   t1, t1, a0          # msb = 31 - lz
    li    t2, 0               # exponent = 0
    li    t3, 0               # overflow = 0
    li    t4, 5
    blt   t1, t4, exact       # msb < 5 go to exact
    addi  t2, t1, -4          # exponent = msb-4
    li    t4, 16
    blt   t2, t4, overflow    # exponent < 16 go to overflow
    li    t2, 15              # exponent = 15
overflow:
    li    t5, 0               # e = 0
overflow_loop:
    bge   t5, t2, estimate    # e >= exponent go to estimate
    slli  t3, t3, 1           # overflow << 1
    addi  t3, t3, 16          # (overflow << 1) + 16
    addi  t5, t5, 1           # e++
    j     overflow_loop       #go to overflow_loop
estimate:
    beqz  t2, exact           #exponent = 0
    bge   s3, t3, exact       #value < overflow go to exact
    addi  t3, t3, -16         #overflow - 16
    srli  t3, t3, 1           #(overflow - 16) >> 1
    addi  t2, t2, -1          # exponent--
    j     estimate            #go to estimate
exact:
    li    t5, 15
    bgeu  t2, t5, exact_done  # if (exponent >= 15) go to exact_done
    slli  t4, t3, 1           # (overflow << 1)
    addi  t4, t4, 16          # next_overflow = (overflow << 1) + 16
    bltu  s3, t4, exact_done  # value < next_overflow go to exact_done
    mv    t3, t4              # overflow = next_overflo
    addi  t2, t2, 1           # exponent++
    j     exact
exact_done:
    sub   t4, s3, t3          # value - overflow
    srl   t4, t4, t2          # (value - overflow) >> exponent
    slli  t2, t2, 4           # exponent << 4
    or    a0, t2, t4          # (exponent << 4) | mantissa
encode_done:
    lw    ra, 0(sp)
    addi  sp, sp, 4
    ret

clz:
    li    t0, 32              # n = 32
    li    t1, 16              # c = 16
c:
    srl   t2, a0, t1          # y = x >> c
    beqz  t2, y               # if (y == 0) skip
    sub   t0, t0, t1          # n -= c
    mv    a0, t2              # x = y
y:
    srli  t1, t1, 1           # c >>= 1
    bnez  t1, c               # while (c)
    sub   a0, t0, a0          # return n - x
    ret

print_int:                     # a0 = int
    li    a7, 1
    ecall
    ret

print_str:                     # a0 = &str
    li    a7, 4
    ecall
    ret

print_nl:  # a0 = &str
    la    a0, nl
    li    a7, 4
    ecall
    ret
```
:::

## Problem c in [Quiz1](https://hackmd.io/@sysprog/arch2025-quiz1-sol)

### Overview
The bfloat16 format (16-bit, from Google Brain) preserves float32’s dynamic range by keeping the same 8-bit exponent, but reduces precision to a 7-bit significand (vs. 23).

Bit Layout
```
┌─────────┬──────────────┬──────────────┐
│Sign (1) │ Exponent (8) │ Mantissa (7) │
└─────────┴──────────────┴──────────────┘
15 14 7 6 0

S: Sign bit (0 = positive, 1 = negative)
E: Exponent bits (8 bits, bias = 127)
M: Mantissa/fraction bits (7 bits)
```
The value 𝑣 of a BFloat16 number is calculated as:
$$v = (-1)^S \times 2^{E-127} \times \left(1 + \frac{M}{128}\right)$$
where:
* $S \in \{0, 1\}$ is the sign bit
* $E \in [1, 254]$ is the biased exponent
* $M \in [0, 127]$ is the mantissa value

Special Cases
* Zero:$E = 0, M = 0 \Rightarrow v = (-1)^S \times 0$
* Infinity: $E = 255, M = 0 \Rightarrow v = (-1)^S \times \infty$
* NaN: $E = 255, M \neq 0 \Rightarrow v = \text{NaN}$
* Denormals: Not supported (flush to zero)

### Special Value Check Function

:::spoiler c code
``` c=
static inline bool bf16_isnan(bf16_t a)
{
    return ((a.bits & BF16_EXP_MASK) == BF16_EXP_MASK) &&
           (a.bits & BF16_MANT_MASK);
}

static inline bool bf16_isinf(bf16_t a)
{
    return ((a.bits & BF16_EXP_MASK) == BF16_EXP_MASK) &&
           !(a.bits & BF16_MANT_MASK);
}

static inline bool bf16_iszero(bf16_t a)
{
    return !(a.bits & 0x7FFF);
}

```
:::

:::spoiler assebly code 
``` risc-v=
# ----------------------------------------
# bf16_isnan(a0 = bf16 bits) -> a0 = 1/0
# ----------------------------------------
bf16_isnan:
    li      t0, BF16_EXP_MASK        # t0 = 0x7F80
    and     t1, a0, t0               # t1 = a & BF16_EXP_MASK
    bne     t1, t0, return_F         # if ((a & EXP_MASK) != EXP_MASK) -> 0
    li      t0, BF16_MANT_MASK       # t0 = 0x007F
    and     t1, a0, t0               # t1 = a & BF16_MANT_MASK
    snez    a0, t1                   # a0 = (t1 != 0) ? 1 : 0
    ret

# ----------------------------------------
# bf16_isinf(a0 = bf16 bits) -> a0 = 1/0
# ----------------------------------------
bf16_isinf:
    li      t0, BF16_EXP_MASK        # t0 = 0x7F80
    and     t1, a0, t0               # t1 = a & BF16_EXP_MASK
    bne     t1, t0, return_F         # if ((a & EXP_MASK) != EXP_MASK) -> 0
    li      t0, BF16_MANT_MASK       # t0 = 0x007F
    and     t1, a0, t0               # t1 = a & BF16_MANT_MASK
    seqz    a0, t1                   # a0 = (t1 == 0) ? 1 : 0
    ret

# ----------------------------------------
# bf16_iszero(a0 = bf16 bits) -> a0 = 1/0
# ----------------------------------------
bf16_iszero:
    li      t0, 0x7FFF               # t0 = 0x7FFF 
    and     t1, a0, t0               # t1 = a & 0x7FFF
    seqz    a0, t1                   # a0 = (t1 == 0) ? 1 : 0
    ret

# ----------------------------------------
# return 0
# ----------------------------------------
return_F:
    li      a0, 0
    ret
```
:::

### Conversion Function
:::spoiler c code
``` c=
```
:::

:::spoiler assebly code
``` risc-v=
# ----------------------------------------
# f32_to_bf16 / bf16_to_f32
# ----------------------------------------

f32_to_bf16:
    srli    t0, a0, 23          # f32bits >> 23
    andi    t0, t0, 0xFF        # (f32bits >> 23) & 0xFF
    li      t1, 0xFF
    beq     t0, t1, bit16       # exponent == 0xFF 
    srli    t2, a0, 16          # t2 = f32bits >> 16
    andi    t2, t2, 1           # t2 = ((f32bits >> 16) & 1)
    li      t3, 0x7FFF
    add     t2, t2, t3
    add     a0, a0, t2
    srli    a0, a0, 16
    ret

bit16:                          # exponent == 0xFF
    srli    a0, a0, 16          # f32bits >> 16
    ret


bf16_to_f32:
    slli    a0, a0, 16
    ret
```
:::


###  Addition and Substraction
:::spoiler c code
``` c=
```
:::

:::spoiler assebly code
``` risc-v=
```
:::

### Multiplication
:::spoiler c code
``` c=
```
:::

:::spoiler assebly code
``` risc-v=
```
:::

### Division
:::spoiler c code
``` c=
```
:::

:::spoiler assebly code
``` risc-v=
```
:::

### Square
:::spoiler c code
``` c=
```
:::

:::spoiler assebly code
``` risc-v=
```
:::


## 5-Stage Pipeline Processor


| Stage   | full name                          | Description                                              |
| ------- |:---------------------------------- |:-------------------------------------------------------- |
| **IF**  | Instruction Fetch                  | Fetch the instruction from memory                        |
| **ID**  | Instruction Decode & Register Read | Decode the instruction and read register operands        |
| **EX**  | Execution or Address Calculation   | Execute the operation or calculate the effective address |
| **MEM** | Data Memory Access                 | Access data memory for load or store instructions        |
| **WB**  | Write Back                         | Write the result back into the destination register      |

1. IF
2. ID
3. EX
4. MEM
5.  WB

| Metric             | Full Name              | Description                 |
|:------------------ |:---------------------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Cycles**         | Clock Cycles           | The total number of clock cycles elapsed since program execution began.                                                                                                                         |
| **Instrs Retired** | Instructions Retired   | The total number of instructions that have successfully passed through all pipeline stages (IF → ID → EX → MEM → WB) and completed execution. Flushed or canceled instructions are not counted. |
| **CPI**            | Cycles Per Instruction | The average number of clock cycles required to complete one instruction. Calculated as **CPI = Cycles ÷ Instrs Retired**. A lower CPI indicates better performance and fewer stalls.            |
| **IPC**            | Instructions Per Cycle | The average number of instructions completed per clock cycle. Calculated as **IPC = Instrs Retired ÷ Cycles**. A higher IPC indicates better parallelism and pipeline efficiency.               |

## Reference
 [Quiz1 of Computer Architecture (2025 Fall)](https://hackmd.io/@sysprog/arch2025-quiz1-sol)
[Pipeline](https://hackmd.io/@ExcitedMail/rynNyOKJY)

