---
# System prepended metadata

title: 'Lab1: RV32I Assembly'

---

## AI Collaboration (Transparency)
1. Use AI for translation and note-taking.
2. Provide debugging suggestions for parts of the code.
3. Improve the formatting and visual presentation of the notes.

# UF8 Encoding/Decoding Tester
 Program Walkthrough (Key Sections)
 Important Registers (Internal Conventions)

s1: Current UF8 value being tested (0..255)

s2: previous_value — the previously decoded value (initialized to -1)

s3 = 15, s4 = 0x7FFF, s5 = 256 — frequently used constants

s6: Pass flag — indicates whether all tests passed (1 = all passed, 0 = any failure)

s7 = &LC0, s8 = &LC1 — pointers to the two error message format strings

s0: Temporary register for the decoded value

s9: Temporary register for the current UF8 byte
### Decode :
```
e  = uf8 >> 4
m  = uf8 & 0xF
base(e) = ((0x7FFF >> (15 - e)) << 4)
value   = base(e) + (m << e)
```
* Implemented using **bit shifts and addition** (no multiplication or division).
* Since the test program decodes right at the beginning, **inlining/merging the decode into the test loop** removes an extra call/jump step.

```
# -------------------------
# Main loop (inline decoding)
# -------------------------
decoding:
        andi    s9, s1, 0xff         # s9 = current UF8 byte (force 0..255)
        srli    a4, s9, 4            # a4 = exponent = uf8 >> 4
        sub     s0, s3, a4           # s0 = 15 - exponent
        sra     s0, s4, s0           # s0 = (0x7FFF >> (15 - exponent))
        andi    a5, s9, 15           # a5 = mantissa = uf8 & 0xF
        slli    s0, s0, 4            # s0 = ((0x7FFF >> (15 - e)) << 4)  -- decode "offset" part
        sll     a5, a5, a4           # a5 = (mantissa << exponent)
        add     s0, s0, a5           # s0 = decoded value (value)

```
### Encode :
Since encoding always requires CLZ, we implement it in the same routine to avoid an extra branch/call.

First, find the appropriate exponent E (Exponent)
```
    mv      t1, a0                 # x
    li      t2, 32                 # n = 32
    li      t3, 16                 # c = 16
1:  srl     t4, t1, t3             # y = x >> c
    beqz    t4, 2f
    sub     t2, t2, t3             # n -= c
    mv      t1, t4                 # x = y
2:  srai    t3, t3, 1              # c >>= 1
    bnez    t3, 1b
    sub     t2, t2, t1             # n -= x
    li      t5, 31
```

$$
\boldsymbol{E}=(31-\mathrm{clz}(\mathrm{value}))-4
$$

This step roughly means: **take the position of the most significant bit (MSB) and subtract 4**,while ensuring that the result is **clamped within the range [0, 15]**.

 ```
    sub     t5, t5, t2             # t5 = 31 - clz(value)
    addi    t5, t5, -4             # t5 = E 
    bltz    t5, 3f
    li      t6, 15
    bleu    t5, t6, 4f
    mv      t5, t6                 # clamp max 15
    j       4f
3:  li      t5, 0
```
Compared to the instructor’s approach, we encode directly via the inverse mapping. We use a CLZ routine to estimate the candidate exponent E, then compute base(E) = ((0x7FFF >> (15 - E)) << 4). Next, we derive the mantissa (denoted M) by simple rearrangement:


$$
base(E) = \left( (0x7FFF \gg (15 - E)) \ll 4 \right)
$$


Calculate the mantissa:

$$
M = \min\left(15,\ \frac{\textit{value}-\textit{base}(E)}{2^{E}}\right)
$$


Combine both parts to form the final 8-bit UF8 value:


$$
uf8 = (E \ll 4) \mid M
$$

According to the previous section, it was found during debugging that for certain values near the segment boundaries, the exponent `E` obtained directly from the CLZ function could be incorrect.

**Example:** when `value = 241`

1. **Find CLZ and estimate E**
   241 in binary: `0b...11110001`
   The highest bit is at bit 7 → `clz(241) = 24`
   Estimated `E = (31 − 24) − 4 = 3`

2. **Compute the segment start base(E)**
   
base(3) = ((0x7FFF >> (15−3)) << 4) = ((0x7FFF >> 12) << 4) = 7 << 4 = **112**
   Next segment:
   
   base(4) = ((0x7FFF >> (15-4)) << 4) = ((0x7FFF >> 11) << 4) = 15 << 4 = ***240***
   

From this example, the correct segment for `241` should actually be at **E = 4**.
To fix this, an additional check mechanism was introduced to ensure that

**value > base(E)** and **value ≤ base(E+1)**

so that the exponent `E` is adjusted correctly for boundary cases.


```
4:

    # ---- base(E) = ((0x7FFF >> (15-E)) << 4) ----
    # a1 = base(E)
    li      a1, 15
    sub     a1, a1, t5             # a1 = 15 - E
    li      a2, 32768
    addi    a2, a2, -1             # a2 = 0x7FFF
    sra     a2, a2, a1
    slli    a2, a2, 4              # a2 = base(E)

    # ---- Segment alignment adjustment: if value < base(E) → E-- (then recompute base) ----
    bltu    a0, a2, 5f
    j       6f
5:  beqz    t5, 6f                 # If E == 0, do not decrement
    addi    t5, t5, -1             # E--
    li      a1, 15
    sub     a1, a1, t5
    li      a2, 32768
    addi    a2, a2, -1
    sra     a2, a2, a1
    slli    a2, a2, 4              # a2 = base(E) (recomputed)
6:
    # ---- If value >= base(E+1) → E++ (then recompute base(E)) ----
    # Compute base(E+1) into a3
    li      a3, 15
    addi    t1, t5, 1              # t1 = E + 1
    bgtu    t1, a3, 7f             # If E == 15, there is no next segment
    sub     a3, a3, t1             # a3 = 15 - (E + 1)
    li      t0, 32768
    addi    t0, t0, -1             # t0 = 0x7FFF
    sra     t0, t0, a3
    slli    t0, t0, 4              # t0 = base(E+1)
    bltu    a0, t0, 7f             # if (value < base(E+1)) keep E
    addi    t5, t5, 1              # E++
    mv      a2, t0                 # a2 = base(E) = base(E+1)
7:
    # ---- M = ((value - base(E)) >> E), clamped to 0..15 ----
    sub     t2, a0, a2             # value - base(E) (guaranteed non-negative)
    srl     t2, t2, t5             # M
    li      t3, 15
    bleu    t2, t3, 8f
    mv      t2, t3
8:
    slli    t5, t5, 4              # (E << 4)
    or      a0, t5, t2             # uf8 = (E << 4) | M
    andi    a0, a0, 0xFF
    ret

```
### **cycle count:**

Using the data provided by the[ **Compiler Explorer**](https://godbolt.org/) website for comparison,
the **cycle count** of the assembly code generated on the webpage is shown below:

![image](https://hackmd.io/_uploads/BkuqALvpeg.png)
The optimized results are shown below:
![image](https://hackmd.io/_uploads/rJcipIwple.png)

The cycles almost reduced 34% using table approach


This part of code can be found on my [github](https://github.com/Stanley0915/ca2025-quizzes/blob/413676c647bbe4f5977e54b87f6b2b096532d2c3/prombleB.s)

[compiler part of problem B](https://github.com/Stanley0915/ca2025-quizzes/blob/2cecf56ff70cdd4f3806fc4e469bd7172ff39da9/compiler_problemB.s)

---
# promble c

### Test whether a bf16 bit-pattern is a special value (Zero, Infinity, NaN)

**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}
$$

To conveniently keep the needed masks, use `s` registers and, before use, spill their contents to memory.

```
 addi   sp,sp,-48
        sw      ra,44(sp)
        sw      s0,40(sp) 
        sw      s1,36(sp)
        sw      s2,32(sp)
        sw      s3,28(sp)
        sw      s4,24(sp)
        sw      s5,20(sp)
        sw      s6,16(sp)
        sw      s7,12(sp)
        sw      s8,8(sp)
        sw      s9,4(sp)
        li s1,0x8000 #define BF16_SIGN_MASK 0x8000U
        li s2,0x7F80 #define BF16_EXP_MASK 0x7F80U
        li s3,0x007F #define BF16_MANT_MASK 0x007FU
        li s4,127    #define BF16_EXP_BIAS 127
        li s5,0x7FC0 #define BF16_NAN()  ((bf16_t){ .bits = 0x7FC0 })
        li s6,0x0000 #define BF16_ZERO() ((bf16_t){ .bits = 0x0000 })

bool_bf16_isnan:
    and a1,s2,a0
    bne s2,a1,label1
    and a2,s3,a0
    bne a2,x0,label1
    li a0,0
    ret
label1:
    li a0,1
    ret
    
bool_bf16_isinf:
    and a1,s2,a0
    bne s2,a1,label2
    and a2,s3,a0
    beq a2,x0,label2
    li a0,1
    ret
label2:
    li a0,0
    ret

bf16_iszero:
    li   t1, 0x7FFF     # load immediate 0x7FFF
    and  a1, a0, t1     # a1 = a0 & t1
    beq  a1,x0,label3
    li   a0,0
    ret
label3:
    li   a0,1
    ret
```

---

This `bf16_add()` function is the **core algorithm** for bfloat16 floating-point addition. It mimics the IEEE-754 addition procedure but implements it using only 16 bits (1 sign + 8 exponent + 7 mantissa). Below is a complete, report-ready description you can paste into documentation. 👇

---

### `bf16_add()` Function Overview

This routine implements addition of two bfloat16 numbers. Because bfloat16 lacks hardware floating-point support, the routine **simulates all floating-point steps using integer bit operations**. The overall logic is divided into **5 stages**:

---

#### Field Extraction

The routine first parses three primary fields from the 16-bit inputs:

* **sign** (bit 15): sign bit
* **exp** (bits [14:7]): exponent with bias 127
* **mantissa** (bits [6:0]): mantissa

It uses shifts and masks (`>>`, `&`) to obtain each field so they can be manipulated independently later.

```
bf16_add:
    srli t0,a0,15  # uint16_t sign_a = (a.bits >> 15) & 1;
    andi t0,t0,1
    srli t1,a1,15  # uint16_t sign_b = (b.bits >> 15) & 1;
    andi t1,t1,1 
    srli t2,a0,7   # int16_t exp_a = ((a.bits >> 7) & 0xFF);
    andi t2,t2,0xFF
    srli t3,a1,7   # int16_t exp_b = ((b.bits >> 7) & 0xFF);
    andi t3,t3,0xFF 
    andi t4,a0,0x7F # uint16_t mant_a = a.bits & 0x7F;
    andi t5,a1,0x7F # uint16_t mant_b = b.bits & 0x7F;
```

---

#### Handling Special Values

If the exponent is all ones (`0xFF`), the number is **NaN or Infinity**:

* If mantissa ≠ 0 → NaN (return `a` directly)
* If the other operand is Inf but with opposite sign → return NaN
* Otherwise return `a` or `b` as is (Inf + Inf of same sign is still Inf)

It also handles zeros: if both exponent and mantissa are 0, directly return the other operand.

```
li      t6, 0xFF
bne     t2, t6, L_expA_not_FF   # exp_a != 0xFF → skip this block
# exp_a == 0xFF
bnez    t4, L_ret_a             # if (mant_a) return a;
# mant_a == 0
bne     t3, t6, L_ret_a         # if (exp_b != 0xFF) return a;
# exp_b == 0xFF
bnez    t5, L_ret_b             # if (mant_b) return b;   // b is NaN
# both are Inf: return ( (sign_a == sign_b) ? b : BF16_NAN() );
bne     t0, t1, L_ret_nan       # sign_a != sign_b → NaN
j       L_ret_b                 # otherwise return b (Inf with same sign)
L_ret_a:
    # a0 already holds a.bits
    ret
L_ret_b:
    mv      a0, a1              # return b.bits
    ret
L_ret_nan:
    li      a0, 0x7FC0          # BF16_NAN()
    ret
```

---

#### Aligning Mantissas

To add the two values on a common scale, the routine computes:

```c
exp_diff = exp_a - exp_b;
```

Then it right-shifts the mantissa of the operand with the **smaller** exponent by `|exp_diff|`.
Example: for 1.0 (Exp=127) + 0.5 (Exp=126), the mantissa of 0.5 shifts right by 1 to align.
If the difference exceeds 8 (limited bfloat16 precision), it returns the larger operand directly, effectively discarding the tiny contribution.

```
sub   a3, t2, t3                      # a3 = exp_diff
bge   x0, a3, skip_exp_diffbigger0    # if (0 >= a3) → skip this block
mv    a4, t2
li    a5, 8
blt   a5, a3, exp_diffbigger8         # if (8 < a3) → return a
srl   t5, t5, a3
j     L_done_block
exp_diffbigger8:
    ret
skip_exp_diffbigger0:
L_done_block:
bge     a3, x0, skip_exp_difflitte0    # if (exp_diff >= 0) → skip this branch
mv      a4, t3
li      t6, -8
blt     a3, t6, exp_difflittle_8       # if a3 < -8 → return b
sub     t6, x0, a3                     # t6 = -exp_diff
srl     t4, t4, t6                     # mant_a >>= t6   (variable shift via srl)
j       L_done_block2
exp_difflittle_8:
    mv      a0, a1                     # return b.bits
    ret
skip_exp_difflitte0:
    mv   a4, t2  # result_exp = exp_a;
L_done_block2:
```

---

#### Perform Add/Sub and Normalize

Depending on whether the signs match, there are two paths:

* **Same-sign addition:**
  Add mantissas and check for **carry** (bit 8 = 1).
  If carry occurs, shift mantissa right by 1 and increment exponent to keep the 1.x format.
  If the exponent overflows to 255 → return ∞.

* **Different-sign subtraction:**
  Subtract the smaller mantissa from the larger. If the result is 0 → return 0.
  Otherwise, enter a while-loop to **left-shift** the mantissa (normalization) until the top bit returns to bit 7, decrementing the exponent each shift.
  If the exponent drops below 0, it’s underflow → return 0.

This corresponds to the floating-point “**align → add/sub → normalize**” sequence.

```
bne t0, t1, sign_a_notequ_sign_b
    mv  s1,t1          # s1 = result_sign
    add s2,t4,t5       # s2 = result_mant
    # t2,t3 are free for reuse now
    andi t2,s2,0x100
    beqz t2, not_equ_1
    srli s2,s2,1
    addi a4, a4, 1     # ++result_exp
    li   t3, 0xFF
    bgeu a4, t3, L_ret_inf  # result_exp >= 0xFF → ±Inf
not_equ_1: 
    j result    
sign_a_notequ_sign_b:
    bgeu    t4, t5, mant_a_notbigger_mant_b  # if (mant_a >= mant_b)
    mv      s1, t1               # result_sign = sign_b
    sub     s2, t5, t4           # result_mant = mant_b - mant_a
    j  mant_a_bigger_mant_b
mant_a_notbigger_mant_b:    
    mv      s1, t0               # result_sign = sign_a
    sub     s2, t4, t5           # result_mant = mant_a - mant_b
mant_a_bigger_mant_b:
    beqz    s2, L_ret_zero 
while_loop:       
    andi    t2, s2, 0x80         # is bit7 = 1?
    bnez    t2, while_loopout    # already normalized → break
    slli    s2, s2, 1            # mant <<= 1
    addi    a4, a4, -1           # --result_exp
    addi    t3, x0, 1
    blt     a4, t3, L_ret_zero   # result_exp <= 0 → underflow → 0
    j       while_loop
while_loopout:
```

---

#### Repack the Result

Finally, repack the result into bfloat16 format:

```
bits = (sign << 15) | ((exp & 0xFF) << 7) | (mant & 0x7F);
```

This yields a new bfloat16 value with correct sign, exponent, and mantissa fields.

```
result:
    slli    t0, s1, 15          # t0 = (result_sign << 15)
    andi    t1, a4, 0xFF        # t1 = result_exp & 0xFF
    slli    t1, t1, 7           # t1 = (result_exp & 0xFF) << 7
    andi    t2, s2, 0x7F        # t2 = result_mant & 0x7F
    or      a0, t0, t1          # a0 = sign | exponent part
    or      a0, a0, t2          # a0 |= mantissa
    li s1,0x8000 #define BF16_SIGN_MASK 0x8000U
    li s2,0x7F80 #define BF16_EXP_MASK 0x7F80U
    ret
```

---

### `bf16_sub()` Function Overview

For subtraction, compute it as addition of the negated subtrahend: flip the sign bit of the second operand with a mask, then reuse the addition routine above.

```
bf16_sub:
    li   t0, 0x8000        # BF16_SIGN_MASK
    xor  a1, a1, t0        # b.bits ^= 0x8000 → flip sign bit
bf16_add:
.....(continue with the addition routine above)
```


---

### bf16_mul() Function Overview

This routine implements **bfloat16 multiplication** entirely using **integer bit operations**, since there’s no hardware floating-point support.
The overall logic is divided into **9 stages**:

---

#### 1) Field Extraction

Parse the 16-bit input into three fields and compute the result sign.

* **sign** (bit 15)
* **exponent** (bits [14:7], bias = **127**)
* **mantissa** (bits [6:0]; normalized numbers will later add the hidden “1” bit)

```
srli  t0, a0, 15          # sign_a
andi  t0, t0, 1
srli  t1, a1, 15          # sign_b
andi  t1, t1, 1
srli  t2, a0, 7           # exp_a
andi  t2, t2, 0xFF
srli  t3, a1, 7           # exp_b
andi  t3, t3, 0xFF
andi  t4, a0, 0x7F        # mant_a
andi  t5, a1, 0x7F        # mant_b
xor   s1, t0, t1          # result_sign = sign_a ^ sign_b
```

---

#### 2) Special-Value Detection (NaN / Inf)

Handle NaN and Inf early to avoid unnecessary multiplications.

Key logic:

* `exp==0xFF && mant!=0` → **NaN** (return that operand)
* `Inf * 0 → NaN`
* `Inf * non-zero → Inf`

```
li    t6, 0xFF
# a special?
bne   t2, t6, mul_check_b_special
bnez  t4, mul_ret_a            # a is NaN
bnez  t3, mul_ret_inf          # b non-zero → ±Inf
bnez  t5, mul_ret_inf          # b subnormal non-zero → ±Inf
j     mul_ret_nan              # Inf * 0 = NaN

# b special?
mul_check_b_special:
bne   t3, t6, mul_check_zero
bjez  t5, 1f                   # b = Inf or NaN?
bnez  t5, mul_ret_b            # b is NaN
1:
bnez  t2, mul_ret_inf          # a non-zero → ±Inf
bnez  t4, mul_ret_inf          # a subnormal non-zero → ±Inf
j     mul_ret_nan              # 0 * Inf = NaN
```

---

#### 3) Zero Detection

If either operand is ±0 → directly return **signed zero**.

```
mul_check_zero:
bnez  t2, mul_check_b_zero
bnez  t4, mul_check_b_zero
j     mul_ret_signed_zero      # a == 0

mul_check_b_zero:
bnez  t3, mul_normalize
bnez  t5, mul_normalize
j     mul_ret_signed_zero      # b == 0
```

---

#### 4) Mantissa Normalization (Handling Subnormal + Hidden Bit)

* For **normalized numbers** (`exp != 0`): set the hidden bit `mant |= 0x80`.
* For **subnormal numbers** (`exp == 0`): left-shift until MSB (bit7) becomes 1, decrement `exp_adjust` each shift, then set `exp = 1`.

```
mul_normalize:
li    t6, 0                   # exp_adjust = 0

# A
bnez  t2, mul_norm_a_done
mul_norm_a_loop:
  andi  s7, t4, 0x80
  bnez  s7, mul_norm_a_subnormal
  slli  t4, t4, 1
  addi  t6, t6, -1            # exp_adjust--
  j     mul_norm_a_loop
mul_norm_a_subnormal:
li    t2, 1
j     mul_norm_b
mul_norm_a_done:
ori   t4, t4, 0x80

# B
mul_norm_b:
bnez  t3, mul_norm_b_done
mul_norm_b_loop:
  andi  s7, t5, 0x80
  bnez  s7, mul_norm_b_subnormal
  slli  t5, t5, 1
  addi  t6, t6, -1            # exp_adjust--
  j     mul_norm_b_loop
mul_norm_b_subnormal:
li    t3, 1
j     mul_mantissa
mul_norm_b_done:
ori   t5, t5, 0x80
```

---

#### 5) Mantissa Multiplication (8-bit × 8-bit, Shift-Add)

Implements an 8×8 multiplication using the shift-add algorithm, producing a 16-bit product stored in `s2`.

```
mul_mantissa:
li    s2, 0                 # result_mant = 0
mv    s7, t4                # multiplicand = mant_a
mv    a5, t5                # multiplier = mant_b
li    a4, 8                 # loop count = 8

mul_shift_add_loop:
  andi  a3, a5, 1
  beqz  a3, mul_shift_only
  add   s2, s2, s7          # if LSB=1, add multiplicand
mul_shift_only:
  slli  s7, s7, 1
  srli  a5, a5, 1
  addi  a4, a4, -1
  bnez  a4, mul_shift_add_loop
```

---

#### 6) Exponent Calculation (Unbiased Form)

`result_exp = (exp_a + exp_b − 127) + exp_adjust`

```
mul_calc_exp:
add   a4, t2, t3            # exp_a + exp_b
add   a4, a4, t6            # + exp_adjust
addi  a4, a4, -127          # − bias (127)
```

---

#### 7) Product Normalization ([1.0, 2.0) Range)

Depending on the highest bit of the product:

* If `bit15=1` → range ≈ **[2.0, 4.0]** → shift right by **8**, increment exponent by **1**.
* Otherwise → range ≈ **[1.0, 2.0]** → shift right by **7**.


```
mul_product_normalize:
li    t6, 0x8000
and   s7, s2, t6
beqz  s7, mul_product_small

# [2.0,4.0) → >>8, exp+1
srli  s2, s2, 8
andi  s2, s2, 0x7F
addi  a4, a4, 1
j     mul_check_overflow

# [1.0,2.0) → >>7
mul_product_small:
srli  s2, s2, 7
andi  s2, s2, 0x7F
```

---

#### 8) Overflow and Underflow Handling

* **Overflow** (`exp >= 255`) → ±Inf
* **Normal** (`exp >= 1`) → pack and return
* **Underflow** (`exp <= 0`) → convert to subnormal by right-shifting `(1−exp)` bits; if too small (`exp < −6`), return ±0.

```
mul_check_overflow:
li    t6, 0xFF
bgeu  a4, t6, mul_ret_inf        # overflow → ±Inf

li    t6, 1
bge   a4, t6, mul_pack_result    # normal → pack

# underflow
mul_handle_underflow:
li    s7, -6
blt   a4, s7, mul_ret_signed_zero  # too small → 0

li    t6, 1
sub   s7, t6, a4                 # shift = 1 - exp
srl   s2, s2, s7                 # mant >>= shift
li    a4, 0                      # exp = 0
```

---

#### 9) Result Packing

Reassemble the 16-bit result: `(sign<<15) | (exp<<7) | mant7`

```
mul_pack_result:
slli  t0, s1, 15              # sign
andi  t1, a4, 0xFF            # exp
slli  t1, t1, 7
andi  t2, s2, 0x7F            # mant
or    a0, t0, t1
or    a0, a0, t2
ret
```

---

#### Special Return Paths (Clean Exit Points)

```
mul_ret_inf:
  slli  t0, s1, 15
  li    t1, 0x7F80            # ±Inf
  or    a0, t0, t1
  ret

mul_ret_nan:
  li    a0, 0x7FC0            # quiet NaN (canonical)
  ret

mul_ret_a:
  ret                          # a0 already holds a.bits

mul_ret_b:
  mv    a0, a1                 # return b.bits
  ret

mul_ret_signed_zero:
  slli  a0, s1, 15             # ±0
  ret
```



---
### `bf16_div()` Function Overview

This routine implements **division** of two **bfloat16** numbers. Since bfloat16 lacks hardware floating-point support, the routine simulates floating-point division entirely with **integer bit operations**. The overall logic is divided into **5 stages**:

---

#### 1) Field Extraction

Parse the 16-bit inputs into three fields:

* **sign** (bit 15): sign bit
* **exp** (bits [14:7]): exponent with **127** bias
* **mant** (bits [6:0]): mantissa (normalized numbers will later add the hidden 1)

Use shifts and masks (>>, &) to obtain each field, and compute the result sign.

```
# Extract fields
srli  t0, a0, 15
andi  t0, t0, 1          # sign_a
srli  t1, a1, 15
andi  t1, t1, 1          # sign_b
srli  t2, a0, 7
andi  t2, t2, 0xFF       # exp_a
srli  t3, a1, 7
andi  t3, t3, 0xFF       # exp_b
andi  t4, a0, 0x7F       # mant_a
andi  t5, a1, 0x7F       # mant_b

# result_sign = sign_a ^ sign_b
xor   s7, t0, t1
```

---

#### 2) Special Values (NaN / Inf / 0)

Handle easy cases first to avoid entering the expensive long-division path.

* **b is NaN / Inf**:

  * `exp_b == 0xFF` and `mant_b != 0` → return **b** (NaN)
  * `exp_b == 0xFF` and `mant_b == 0` (b = Inf):

    * if a is NaN → return **a**
    * if a is not Inf → result is **0** (a/Inf = 0)
    * if a = Inf → **NaN** (Inf/Inf)
* **b is 0**:

  * if a is also 0 → **NaN** (0/0)
  * otherwise result is **Inf** (with the computed sign)
* **a is NaN / Inf**:

  * `exp_a == 0xFF` and `mant_a != 0` → return **a** (NaN)
  * `exp_a == 0xFF` and `mant_a == 0` (a = Inf, b not special and nonzero) → **Inf**
* **a is 0**: directly return **±0** (with the computed sign)

```
li   t6, 0xFF
# Check b first
bne  t3, t6, div_b_not_special
bnez t5, div_ret_b          # b is NaN
bne  t2, t6, div_ret_zero   # a is not Inf → 0
bnez t4, div_ret_a          # a is NaN
j    div_ret_nan            # Inf/Inf = NaN

div_b_not_special:
or   s8, t3, t5
beqz s8, div_b_zero         # b==0 → a/0

# Then check a
bne  t2, t6, div_a_not_special
bnez t4, div_ret_a          # a is NaN
j    div_ret_inf            # Inf / b = Inf

div_a_not_special:
or   s8, t2, t4
beqz s8, div_ret_zero       # a==0 → 0
```

---

#### 3) Mantissa Normalization (add hidden bit / handle subnormals)

* Normal numbers (`exp != 0`): **add the hidden 1** (`mant |= 0x80`)
* Subnormals (`exp == 0`): this version uses a simplified model—**set exp=1** and **do not add** the hidden bit for the division

```
# A
beqz  t2, div_norm_a
ori   t4, t4, 0x80
j     div_norm_a_done
div_norm_a:
li    t2, 1
div_norm_a_done:

# B
beqz  t3, div_norm_b
ori   t5, t5, 0x80
j     div_norm_b_done
div_norm_b:
li    t3, 1
div_norm_b_done:
```

---

#### 4) Long Division (restoring division): mant_a / mant_b

Implement a 16-step restoring division using shift-subtract to obtain **quotient** and **remainder**:

* `dividend = mant_a << 15`
* stepwise compare `dividend` with `divisor << shift`; subtract when possible and set the quotient bit
* after 16 iterations, get a **16-bit quotient** in `a4`

```
slli  s8, t4, 15         # dividend
mv    s9, t5             # divisor
li    a4, 0              # quotient
li    a5, 16             # 16 iterations

div_loop:
  slli  a4, a4, 1
  addi  a6, a5, -1
  li    t6, 15
  sub   t6, t6, a6       # shift = i
  sll   a7, s9, t6       # divisor << shift
  bgeu  s8, a7, div_can_sub
  j     div_next
div_can_sub:
  sub   s8, s8, a7       # dividend -= (divisor<<shift)
  ori   a4, a4, 1        # quotient |= 1
div_next:
  addi  a5, a5, -1
  bnez  a5, div_loop
```

Compute the **result exponent**: `exp = (exp_a - exp_b) + bias` (bias = 127)

```
sub   a5, t2, t3         # exp_a - exp_b
addi  a5, a5, 127        # + bias
```

---

#### 5) Normalize Quotient, Check Over/Underflow, Pack Result

##### 5.1 Normalize the Quotient

* If the quotient MSB (bit15) is 1 → range ≈ **[1.0, 2.0)**, shift right **8** to get a 7-bit mantissa
* Otherwise (value < 1.0) → **left-shift until MSB=1**, decrement exponent by **1** per shift, then shift right 8 to get the mantissa

```
li   t6, 0x8000
and  s8, a4, t6
beqz s8, div_quo_small
  # quotient >= 0x8000 → [1,2]
  srli  a4, a4, 8
  andi  a4, a4, 0x7F
  j     div_check_overflow

div_quo_small:
# Left-shift until MSB=1 (only shift if exp>1 to avoid excessive underflow)
div_norm_quo:
  li    t6, 0x8000
  and   s8, a4, t6
  bnez  s8, div_norm_quo_done
  li    t6, 1
  ble   a5, t6, div_norm_quo_done
  slli  a4, a4, 1
  addi  a5, a5, -1
  j     div_norm_quo
div_norm_quo_done:
  srli  a4, a4, 8
  andi  a4, a4, 0x7F
```

##### 5.2 Overflow / Underflow

* **Overflow** (`exp >= 255`): return **±Inf**
* **Underflow** (`exp <= 0`): this version directly returns **±0** (simplified; a stricter implementation would output **subnormals** with rounding)

```
li   t6, 0xFF
bgeu a5, t6, div_ret_inf   # overflow → Inf
blez a5, div_ret_zero      # underflow → 0
```

##### 5.3 Pack the Result

Fill sign, exponent, and mantissa back into the bfloat16 layout:

```
slli  t0, s7, 15          # sign
andi  t1, a5, 0xFF        # exp
slli  t1, t1, 7
andi  t2, a4, 0x7F        # mant
or    a0, t0, t1
or    a0, a0, t2
ret
```

---

#### Special Return Paths (clean fast-exits)

```
div_b_zero:
  or   s8, t2, t4
  beqz s8, div_ret_nan     # 0/0 = NaN
  j    div_ret_inf         # a/0 = Inf

div_ret_inf:
  slli a0, s7, 15
  li   t0, 0x7F80
  or   a0, a0, t0
  ret

div_ret_nan:
  li   a0, 0x7FC0          # canonical qNaN
  ret

div_ret_zero:
  slli a0, s7, 15          # ±0
  ret

div_ret_a:
  ret                       # a0 already holds a

div_ret_b:
  mv   a0, a1               # return b
  ret
```


---

Because of time constraints and limited personal capability, the square root (sqrt) part could not be completed.
Therefore, in the testing section, the sqrt function will be omitted from the comparisons for now.
![image](https://hackmd.io/_uploads/r1RYYdq6ee.png)
![image](https://hackmd.io/_uploads/BkAcKOcTel.png)
The cycle count after omitting the sqrt part is 1517.

This part of the code can be found on my [github](https://github.com/Stanley0915/ca2025-quizzes/blob/413676c647bbe4f5977e54b87f6b2b096532d2c3/problemC.s)



---
# Leetcode 283 Move Zeroes
Given an integer array nums, move all 0's to the end of it while maintaining the relative order of the non-zero elements.

Note that you must do this in-place without making a copy of the array.

 

Example 1:

Input: nums = [0,1,0,3,12]
Output: [1,3,12,0,0]
Example 2:

Input: nums = [0]
Output: [0]

### C program
```c
void moveZeroes(int* nums, int numsSize) {
    int zerohead=0;
    for(int i=0;i<numsSize;i++){
        if(nums[i]!=0){
            if(i!=zerohead){
                nums[zerohead]=nums[i];
                nums[i]=0;
                  
            }
            zerohead++;
        }
    }
}
```
### Assembly code (RISC-V)

```
.data
test1:      .word 0, 1, 0, 3, 12
test1_size: .word 5
test1_msg:  .string "Test 1: [0, 1, 0, 3, 12] => "

test2:      .word 0, 0, 0, 0
test2_size: .word 4
test2_msg:  .string "Test 2: [0, 0, 0, 0] => "

test3:      .word 5, 7, 2, 9, 1
test3_size: .word 5
test3_msg:  .string "Test 3: [5, 7, 2, 9, 1] => "

newline:    .string "\n"
space:      .string " "
bracket_open: .string "["
bracket_close: .string "]"

.text
.globl main

main:
    addi sp, sp, -4
    sw ra, 0(sp)
    
    # test 1
    la a0, test1_msg
    li a7, 4
    ecall
    
    la a0, test1
    lw a1, test1_size
    jal moveZeroes
    
    la a0, test1
    lw a1, test1_size
    jal print_array
    
    # test 2
    la a0, test2_msg
    li a7, 4
    ecall
    
    la a0, test2
    lw a1, test2_size
    jal moveZeroes
    
    la a0, test2
    lw a1, test2_size
    jal print_array
    
    # test 3
    la a0, test3_msg
    li a7, 4
    ecall
    
    la a0, test3
    lw a1, test3_size
    jal moveZeroes
    
    la a0, test3
    lw a1, test3_size
    jal print_array
    
    lw ra, 0(sp)
    addi sp, sp, 4
    li a7, 10
    ecall
# moveZeroes 函數
# 輸入: a0 = nums 陣列指標, a1 = numsSize
# 使用的暫存器:
#   t0 = zerohead 
#   t1 = i 
#   t2 = nums[i] 
#   t4 = nums 陣列基底位址
moveZeroes:
    li t0, 0            
    li t1, 0            
    mv t4, a0               
loop:
    bge t1, a1, end  
    
   
    slli t3, t1, 2      # t3 = i * 4 (因為 int 是 4 bytes)
    add t3, t4, t3      # t3 = &nums[i]
    lw t2, 0(t3)        # t2 = nums[i]
    
   
    beqz t2, continue   
    
    beq t1, t0, skip_swap
    
    slli t5, t0, 2      
    add t5, t4, t5   
    sw t2, 0(t5) 
    
    sw zero, 0(t3)      
    
skip_swap:
    addi t0, t0, 1  
    
continue:
    addi t1, t1, 1  
    j loop
    
end:
    ret

print_array:
    addi sp, sp, -16
    sw ra, 12(sp)
    sw s0, 8(sp)
    sw s1, 4(sp)
    sw s2, 0(sp)
    
    mv s0, a0           # s0 = 陣列指標
    mv s1, a1           # s1 = 陣列大小
    li s2, 0            # s2 = 索引
    
    # 印出 "["
    la a0, bracket_open
    li a7, 4
    ecall
    
print_loop:
    bge s2, s1, print_end
    
    slli t0, s2, 2
    add t0, s0, t0
    lw a0, 0(t0)
    li a7, 1
    ecall
    
    # 如果不是最後一個元素，印出空格
    addi t0, s1, -1
    bge s2, t0, skip_space
    la a0, space
    li a7, 4
    ecall
    
skip_space:
    addi s2, s2, 1
    j print_loop
    
print_end:
    # 印出 "]"
    la a0, bracket_close
    li a7, 4
    ecall
    
    # 印出換行
    la a0, newline
    li a7, 4
    ecall
    
    lw ra, 12(sp)
    lw s0, 8(sp)
    lw s1, 4(sp)
    lw s2, 0(sp)
    addi sp, sp, 16
    ret
```
result:
![image](https://hackmd.io/_uploads/H1a4Gdjagx.png)



---
# Ripes Simulator
using 5-stage pipelined process in pipes
```
00000074 <bool_bf16_isnan>:
    74:        00a975b3        and x11 x18 x10
    78:        00b91a63        bne x18 x11 20 <label1>
    7c:        00a9f633        and x12 x19 x10
    80:        00061663        bne x12 x0 12 <label1>
    84:        00000513        addi x10 x0 0
    88:        00008067        jalr x0 x1 0
```
take 7c:00a9f633  and x12 x19 x10for example in this 5-stage pipelined section.


---
**IF**
![image](https://hackmd.io/_uploads/rJnwFmKagg.png)
*The program counter updates the value of the instruction each time to fetch a new instruction.
*The current instruction memory address is 0x0000008c, and the instruction read from that address is 0x00a975b3.
*Since the current instruction memory address is 0x0000004c, and most instructions in many architectures (such as RISC-V) are 4 bytes (32 bits) long, the next instruction address will typically be 0x0000004c, which is 4 bytes ahead of the current one.

---
**ID**
![image](https://hackmd.io/_uploads/Sk4O5mFagx.png)

*decode the instruction 0x00a975b3 into binary 0000000 01010 10010 111 01011 0110011

funct7 = 0000000
rs2 = 01010
rs1 = 10010
funct3 = 111
rd = 01011
opcode = 0110011
The instruction 00a9f633 has been decoded as and x12 x19 x10

---
**EXE**
![image](https://hackmd.io/_uploads/rk3o5XYaxe.png)2
*In the EX (Execute) stage, the ALU performs a bitwise AND operation between the value stored in register x18 and the value in register x10. The result of this operation is then forwarded to be written back into register x11

---
**MEM**
![image](https://hackmd.io/_uploads/BJkC9XKpxg.png)
*In the MEM (memory) stage, it checks whether to write to or read from memory. For the and instruction, there is no need for reading from or writing to memory.

---
**WB**
![image](https://hackmd.io/_uploads/S14ksmtTgx.png)
*In the WB (write-back) stage, the data is written back to register x11