---
# System prepended metadata

title: arch2025-homework1
tags: [computer architecture]

---

# Assignment1: RISC-V Assembly and Instruction Pipeline

contributed by [rrrchii](https://github.com/rrrchii/ca2025-quizzes/tree/main)

---

## Quiz 1 Prob B : UF8 ↔ Unsigned Integer Conversion

---

### Overview

**This problem is about implementing an a code between a 20-bit unsigned integer and an 8-bit uf8 format.**

**Input：** 20-bit unsigned integer

**Output：** 8-bit uf8 value, packs 4-bit exponent (e) and 4-bit mantissa (m)

```c
[ e e e e ][ m m m m ]
   high 4     low 4
```

### Decode (uf8 → uint32)
---
Given an 8-bit uf8 value $b$:

- $\text{offset}=(2e−1)\cdot16$
- $D(b) = m \cdot 2^e + (2^e - 1) \cdot 16$

:::spoiler C Code
```c
uint32_t uf8_decode(uf8 fl)
{
    uint32_t mantissa = fl & 0x0f;     // extract the last 4 bit for mantissa part
    uint8_t exponent = fl >> 4;        // extract the last 4 bit for exponent part 
    uint32_t offset = (0x7FFF >> (15 - exponent)) << 4;    
      /* e = 0, offset = 0 = 0x7FFF >> 15 = 0x0 0
         e = 1, offset = 1 = 0x7FFF >> 14 = 0x1 0
         e = 2, offset = 3 = 0x7FFF >> 13 = 0x3 0 ... */
    
    return (mantissa << exponent) + offset; //  m << e means m * 2^e
}
```
:::
### Encode (uint32 → uf8)
---
Given an unsigned integer $v$ : 

$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$
:::spoiler C Code
```c
uf8 uf8_encode(uint32_t value)
{
    /* Use CLZ for fast exponent calculation */
    if (value < 16)
        return value;       // if value < 16 return value

    /* Find appropriate exponent using CLZ hint */
    int lz = clz(value);    // counting leading zeros
    int msb = 31 - lz;      // get MSB

    /* Start from a good initial guess */
    uint8_t exponent = 0;
    uint32_t overflow = 0;  // overflow is offset(e)

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

        /* Calculate overflow for estimated exponent */
        for (uint8_t e = 0; e < exponent; e++)
            overflow = (overflow << 1) + 16;    
              /* e = 0, overflow = 16
                 e = 1, overflow = 16 * 2 + 16 = 48 
                 e = 2, overflow = 48 * 2 + 16 = 112 ... */

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

    /* Adjust up if estimate was to small*/
    while (exponent < 15) {
        uint32_t next_overflow = (overflow << 1) + 16;
        if (value < next_overflow)
            break;
        overflow = next_overflow;
        exponent++;
    }
    // condition [ overflow < value < next_overflow ] is satisfied and find exact 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++) {  // range for all possible uf8 value
        uint8_t fl = i;
        int32_t value = uf8_decode(fl); // decode the 8 bits value
        uint8_t fl2 = uf8_encode(value); // encode it back

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

        if (value <= previous_value) {  // Verify that it's strictly 
            printf("%02x: value %d <= previous_value %d\n", fl, value,
                   previous_value); increasing
            passed = false;
        }

        previous_value = value;
    }

    return passed;
}
```
:::
### Note
---
- **Test Data**
    - These values are the exact integers that the UF8 format can represent without any loss after a round-trip conversion.
      ```
      0,1,2,...,15, //step = 1
      16,18,20,...,46, //step = 2
      48,52,56,...,108, //step = 4
      112,120,128,...,232,
      240,256,272,... (until exponent=15)
      ```
- **Encoding and Decoding Behavior**
    - If the unsigned integer `v < 16`, return `v` directly.
        - Example: `v = 7 → 0x07`
        Decode : 
        $m = 7, e = 0 ⇒ D(0x07) = 7 \cdot2^0+(2^0-1)\cdot16 = 7$ 
    - For larger values, the exponent `e` determines the segment.
        - Example: `v = 52 = 0b110100`
            - MSB = 5
            - Exponent: e = 5 - 4 = 1
            - Segment offset: offset(e = 1) = 16
            - next offset: offset(e = 2) = 48 (52>48)
            - adjust exponent to 2
            - new offset 48 ~ 112
            - Encoding : 
            $mantissa = \lfloor(v - \text{offset}(e))/2^e\rfloor=\lfloor(52             - 48)/2^2\rfloor = 1$
            $E(52)= \ \ (e<<4) \ | \ m \ \ = \ \ (2<<4) \ | \ 2 \ = \ 0x21$
            Decoding : 
            $m = 1, e = 2 ⇒ D(0x21) = 2 \cdot2^2+(2^2-1)\cdot16 = 8+48=56$ 
            (Decoded value is slightly less than the original value, $b<v$)
- **Exponent and Segment Relationship**
    - Each exponent e defines a segment of length: $16 × 2^e$
    - Larger exponents correspond to larger segment lengths.
- **Immediate Range**
    -  The representable immediate value range is: $-2048 \ to \ 2047$
- **Rationale for Encoding After Decoding in Tests**
    - The tests perform encode-after-decode to ensure that all values within a practical range are covered.
    - An upper limit of 256 is used because this encompasses all possible UTF-8 byte values (0–255), ensuring comprehensive validation across typical encoding scenarios.


### Implementation（實作）
---

#### Execution info for C code (using rv64i compiler)
I can't find the `riscv32-unknown-elf-gcc.exe` that adapt to windows environment.
Is there any method to fix this issue?
I use the method of this page to install `riscv64-unknown-elf-gcc.exe`
https://hackmd.io/@accomdemy/BJprQ8Xjc/https%3A%2F%2Fhackmd.io%2F%40accomdemy%2FSyoatR-sc
![image](https://hackmd.io/_uploads/Hkb8iiEael.png)

#### [RISC-V](https://github.com/rrrchii/ca2025-quizzes/blob/main/uf8_to_uint32_last_version.s)
:::spoiler Assembly code
```
           .data
 text1:      .asciz ": produces value "
 text2:      .asciz " but encodes back to "
 text3:      .asciz ": value "
 text4:      .asciz " <= previous_value "
 text5:      .asciz "tests pass."
 text6:      .asciz "some tests failed."

 newline:    .asciz "\n"
    
    .text
    .globl main
main:
    jal ra, test
    beq a0, x0, fail       # a0 is passed flag if (passed == 0) means check fail
    la    a0, text5
    li    a7, 4
    ecall                  # print "tests pass."
    la    a0, newline
    li    a7, 4 
    ecall                  # print "\n"
    li    a0, 0            # a0 return 1 means success
    li    a7, 10
    ecall                  # exit
    
test:
    addi  sp, sp, -4
    sw    ra, 0(sp)        # protect ra
    addi  s0, x0, -1       # previous value   
    li    s1, 1            # defult s1(passed flag) = 1 mean ture
    li    s2, 0            # counter i
    li    s3, 256          # end of counter i

test_loop:
    bgeu  s2, s3, return_passed
    mv    a0, s2
    jal   ra, uf8_decode   
    mv    s4, a0           # s4 decode value
    jal   ra, uf8_encode
    mv    s5, a0           # the value has been decode and encode
    
check_1:
    beq   s2, s5, check_2  # if s2 == s5 mean check_1 success 
    mv    a0, s2
    li    a7, 34           
    ecall                  # print "i" in hex
    la    a0, text1         
    li    a7, 4
    ecall                  # print "produces value"
    mv    a0, s4           
    li    a7, 1
    ecall                  # print "value" in dec
    la    a0, text2
    li    a7, 4
    ecall                  # print "but encodes back to"
    mv    a0, s5           
    li    a7, 34
    ecall                  # print the "value" that has been decode and encode in hex
    la    a0, newline
    li    a7, 4
    ecall                  # print "\n" for newline
    li    s1, 0            # set s1 = 0 means passed = false
    
check_2:
    bgt   s2, s0, check_done
    mv    a0, s2
    li    a7, 34
    ecall                  # print "i" in hex
    la    a0, text3
    li    a7, 4            
    ecall                  # print ": value"
    mv    a0, s4           
    li    a7, 1
    ecall                  # print "value" in dec
    la    a0, text4
    li    a7, 4
    ecall                  # print " <= previous_value "
    mv    a0, s0           
    li    a7, 34
    ecall                  # print "previous_value value" in hex
    la    a0, newline      
    li    a7, 4
    ecall                  # print newline
    li    s1, 0            # set s1 = 0 means passed = false
    
check_done:
    mv    s0, s2           # to check the increase
    mv    a0, s1           # return passed
    addi  s2, s2, 1
    j     test_loop

fail:
    la    a0, text6
    li    a7, 4
    ecall                  # print "some tests failed."
    la    a0, newline
    li    a7, 4
    ecall                  # print "\n"
    li    a0, 1            # a0 return 1 means fail
    li    a7, 10
    ecall                  # exit
    
return_passed:
    lw    ra, 0(sp)        # load back the return address from main:
    addi  sp, sp, 4
    ret
clz:
    beq   a0, x0, zero      # if (x == 0) return 32

    li    t0, 0             # n = 0
    
    srli  t1, a0, 16
    bne   t1, x0, chk8
    addi  t0, t0, 16
    slli  a0, a0, 16        # if ((x >> 16) == 0) { n += 16; x <<= 16; }

chk8:
    srli  t1, a0, 24
    bne   t1, x0, chk4
    addi  t0, t0, 8
    slli  a0, a0, 8          # if ((x >> 24) == 0) { n += 8; x <<= 8; }

chk4:
    srli  t1, a0, 28
    bne   t1, x0, chk2
    addi  t0, t0, 4
    slli  a0, a0, 4          # if ((x >> 28) == 0) { n += 4; x <<= 4; }

chk2:
    srli  t1, a0, 30
    bne   t1, x0, chk1
    addi  t0, t0, 2
    slli  a0, a0, 2           # if ((x >> 30) == 0) { n += 2; x <<= 2; }

chk1:
    srli  t1, a0, 31
    bne   t1, x0, ret
    addi  t0, t0, 1           # if ((x >> 31) == 0) { n += 1; }

ret:
    mv    a0, t0               # return n
    ret

zero:
    li    a0, 32
    ret

    
    
uf8_decode:
    andi  t0, a0, 0x0F     # t0 is mantissa
    srli  t1, a0, 4        # t1 is exponent
    
    li    t2, 15
    sub   t2, t2, t1       # t2 = 15 - exponent
    
    li    t3, 0x7FFF
    srl   t3, t3, t2
    slli  t3, t3, 4        # t3 is offset
    
    sll   t0, t0, t1       # mantissa << exponent
    add   a0, t0, t3       # fl = (mantissa << exponent) + offset    
    
    ret
    
    
uf8_encode:
    addi  sp, sp, -4
    sw    ra, 0(sp)        # because it will call CLZ in this function
    
    li    t4, 16           # t4 = 16
    bltu  a0, t4, fast_return    # if( value < 16 ) return value;
    
    mv    t5, a0           # t5 = value use t5 input clz
    jal   ra, clz
    mv    t0, a0           # t0 = lz
    li    t1, 31
    sub   t1, t1, t0       # t1 = msb
    
    li    t2, 0            # exponent = 0
    li    t3, 0            # overflow = 0
    
    li    t4, 5            # t6 = 5
    blt   t1, t4, after_adjust_down
    addi  t2, t1, -4       # exponent = msb - 4
    
    li    t4, 15           # t6 = 15
    bleu  t2, t4, exp_ok   # if(exp > 15)
    li    t2, 15           # exp = 15
    
exp_ok:   
    li     t4, 0            # t4 = counter e = 0
    
build_overflow:
    bgeu   t4, t2, adjust_overflow
    slli   t3, t3, 1       # overflow <<= 1
    addi   t3, t3, 16      # (overflow << 1) + 16
    addi   t4, t4, 1       # e ++
    j      build_overflow
    
adjust_overflow:

adjust_down:
    beq    t2, x0, after_adjust_down     # exp > 0
    bgeu   t5, t3, after_adjust_down     # value < overflow
    addi   t3, t3, -16                   # overflow - 16
    srli   t3, t3, 1                     # (overflow - 16) >> 1
    addi   t2, t2, -1                    # exp --
    j      adjust_down

after_adjust_down:
    li     t6, 15
    li     t4, 0
    
adjust_up:
    bgeu   t2, t6, after_estimate_overflow
    slli   t4, t3, 1                     # next_overflow = (overflow << 1)
    addi   t4, t4, 16                    # next_overflow = (overflow << 1) + 16
    blt    t5, t4, after_estimate_overflow
    mv     t3, t4                        # overflow = next_overflow
    addi   t2, t2, 1                     # exp ++
    j      adjust_up

after_estimate_overflow:
    
ret_val:
    sub    t5, t5, t3
    srl    t5, t5, t2                    # t5 is mantissa
    
    slli   a0, t2, 4
    or     a0, a0, t5
    
fast_return:
    lw ra, 0(sp)
    addi sp, sp, 4
    ret
```
:::
#### Execution info for RISC-V code
![image](https://hackmd.io/_uploads/SJCXO7STex.png)


### Improvment of CLZ
---
#### Analysis
Both functions compute the number of leading zeros in a 32-bit integer. The key differences are control flow and dependencies.

#### Looped version (`do { ... } while (c)`)
- Each iteration does:
  1) `y = x >> c`
  2) branch on `if (y)` (data-dependent)
  3) `c >>= 1`
  4) loop back branch `while (c)` (**backward branch**)
- Up to 5 iterations (c=16,8,4,2,1), **2 branches per iteration** → many branches including a back-edge.
- `x` and `c` are updated each round → **loop-carried dependencies**, limiting ILP and scheduling.
- Ends with `return n - x;` (a trick that reduces readability and may hinder optimization).

#### Branch-and-shift (16/8/4/2/1) version
```c
static inline unsigned clz32(uint32_t x) {
    if (x == 0) return 32;

    unsigned n = 0;
    if ((x & 0xFFFF0000u) == 0) { n += 16; x <<= 16; }
    if ((x & 0xFF000000u) == 0) { n += 8;  x <<= 8;  }
    if ((x & 0xF0000000u) == 0) { n += 4;  x <<= 4;  }
    if ((x & 0xC0000000u) == 0) { n += 2;  x <<= 2;  }
    if ((x & 0x80000000u) == 0) { n += 1; }
    return n;
}
```
#### Execution info for RISC-V code after improvment
![image](https://hackmd.io/_uploads/rJVbr796ll.png)
improve cycle count about 12%

---

## Quiz 1 Prob C : BF16 ↔ FP32 Conversion

---
### Note
- **Subnormal (Denormalized) Values**
    - Definition:
      - the exponent field = 0 (but not all bits are 1)
      - the mantissa (fraction) ≠ 0.
    - Purpose:
        - Subnormal numbers allow for a **gradual underflow** between zero   and the smallest normalized value.

### BF16 Conversion and Classification Utilities
:::spoiler C code
```c
#include <stdbool.h>
#include <stdint.h>
#include <string.h>

typedef struct {
    uint16_t bits;
} bf16_t;

#define BF16_SIGN_MASK 0x8000U
#define BF16_EXP_MASK 0x7F80U
#define BF16_MANT_MASK 0x007FU
#define BF16_EXP_BIAS 127

#define BF16_NAN() ((bf16_t) {.bits = 0x7FC0}) //exp = all 1, mantissa != 0
#define BF16_ZERO() ((bf16_t) {.bits = 0x0000})

static inline bool bf16_isnan(bf16_t a)
{
    return ((a.bits & BF16_EXP_MASK) == BF16_EXP_MASK) &&
           (a.bits & BF16_MANT_MASK); //exp == all 1, mantissa != 0
}

static inline bool bf16_isinf(bf16_t a)
{
    return ((a.bits & BF16_EXP_MASK) == BF16_EXP_MASK) &&
           !(a.bits & BF16_MANT_MASK); // exp == all 1, mantissa == 0
}

static inline bool bf16_iszero(bf16_t a)
{
    return !(a.bits & 0x7FFF); // exp == 0, mantissa == 0
}

static inline bf16_t f32_to_bf16(float val)
{
    uint32_t f32bits;
    memcpy(&f32bits, &val, sizeof(float)); // copy float val in bit pattern to uint32 called f32bits
    if (((f32bits >> 23) & 0xFF) == 0xFF)  // exp = all 1
        return (bf16_t) {.bits = (f32bits >> 16) & 0xFFFF};
    f32bits += ((f32bits >> 16) & 1) + 0x7FFF; // ties-to-even
    return (bf16_t) {.bits = f32bits >> 16}; // 判斷完進位後回傳高16位
}

static inline float bf16_to_f32(bf16_t val)
{
    uint32_t f32bits = ((uint32_t) val.bits) << 16; // bf16 value left shift 16 bits
    float result;
    memcpy(&result, &f32bits, sizeof(float));// copy float val in bit pattern to uint32 called f32bits
    return result; 
}
```
:::
#### Add and Sub
##### Note
- **Rounding Rule — Ties to Eve**
 
| low 16 bits| high last bit | carry or not |
| ---------- | --------      | -------------|
| `< 0x8000` |               | ❌           | 
| `= 0x8000` | 0             | ❌           |
| `= 0x8000` | 1             | ✅           |
| `> 0x8000` |               | ✅           |

This rule ensures that rounding produces the nearest even result, avoiding bias over multiple operations.
- **Bitwise Copy for Floating-Point Reinterpretation**
    - When reinterpreting floating-point bits (for example, from uint32_t to float), direct casting may lead to undefined behavior.
    - To ensure correct bitwise reinterpretation, use memcpy:
    ```memcpy(&result, &f32bits, sizeof(float));```
    This copies the exact bit pattern from f32bits into result without altering the binary layout. 
- **Normalizing the Mantissa**
    - Once the number is confirmed to be within the representable range, we must restore the implicit leading `1` of the mantissa for normalized values:
    `mant_a |= 0x80;`
    This makes the mantissa represent $1.mantissa$ instead of $0.mantissa$,
which is required for correct reconstruction:
$(-1^{sign} \ × \ (1.mantissa) \ × \ 2^{(exp-bias)})$
Without this step, the computation would represent a subnormal (denormalized) value incorrectly.
- **BF16 Addition — Exponent Alignment**

| condition       | meaning         | motion                 | math                            |
| --------------- | -------         | --------------------   | ------------                    |
| `exp_diff > 0`  | a bigger that b | `mant_b >>= exp_diff`  | Align exp_b to exp_a            |
| `exp_diff < 0`  | b bigger that a | `mant_a >>= -exp_diff` | Align exp_a to exp_b            |
| `exp_diff > 8`  | b is too small  | `return a`             | b can't influence the result    |
| `exp_diff < -8` | a is too small  | `return b`             | b can't influence the result    |
| `exp_diff == 0` | already align   | no need movement       | already align just add a and b  |

After alignment, both mantissas share the same exponent base, allowing direct addition or subtraction.
#### C Code
::: spoiler C code
```c
static inline bf16_t bf16_add(bf16_t a, bf16_t b)
{
    uint16_t sign_a = (a.bits >> 15) & 1;   
    uint16_t sign_b = (b.bits >> 15) & 1; // get a, b sign bit
    int16_t exp_a = ((a.bits >> 7) & 0xFF);
    int16_t exp_b = ((b.bits >> 7) & 0xFF); // get a, b exp bits
    uint16_t mant_a = a.bits & 0x7F;
    uint16_t mant_b = b.bits & 0x7F; // get a, b mantissa bits

    if (exp_a == 0xFF) { // a is Inf/NaN
        // a is NaN
        if (mant_a)     
            return a;
        // a is Inf
        if (exp_b == 0xFF) 
            return (mant_b || sign_a == sign_b) ? b : BF16_NAN(); 
        /* mant_b != 0  b is NaN so a+b is NaN
         * sign_a == sign_b  a,b are +Inf  a+b = +Inf (same as negative)
         * sign_a != sing_b  a is +Inf b is _Inf a+b = NaN */
        
        return a;
    }
    
    /* Summary of specials handled above/below:
       Inf  +  NaN = NaN
       NaN  +  NaN = NaN
      +Inf  + +Inf = +Inf
      -Inf  + -Inf = -Inf
      +Inf  + -Inf = NaN
    */
    
    if (exp_b == 0xFF)
        return b; // b is NaN so a+b is NaN
    if (!exp_a && !mant_a)
        return b; // a = 0 a+b = 0+b = b return b
    if (!exp_b && !mant_b)
        return a; // b = 0 a+b = a+0 = a return a
    if (exp_a)
        mant_a |= 0x80; // exp_a != 0 把mantissa最開頭的1補上，確保後面計算正確
    if (exp_b)
        mant_b |= 0x80; // exp_b != 0 把mantissa最開頭的1補上，確保後面計算正確
    int16_t exp_diff = exp_a - exp_b; // difference between exp_a and exp_b
    uint16_t result_sign;
    int16_t result_exp;
    uint32_t result_mant;

    if (exp_diff > 0) { // define exponent bits and align mantissa bits
        result_exp = exp_a; // a is bigger so use exp_a
        if (exp_diff > 8)
            return a;
        mant_b >>= exp_diff; // align mant_b to mant_a
    } else if (exp_diff < 0) {
        result_exp = exp_b; // b is bigger so use exp_b
        if (exp_diff < -8)
            return b;
        mant_a >>= -exp_diff; // align mant_a to mant_b
    } else {
        result_exp = exp_a; // exp_a == exp_b so use exp_a
    }

    if (sign_a == sign_b) { 
        result_sign = sign_a;
        result_mant = (uint32_t) mant_a + mant_b; // a and b have same sign so we can add mantissa

        if (result_mant & 0x100) { // if addition of mantissa bigger than 0xFF
            result_mant >>= 1; // result_mant right shift 1 bit
            if (++result_exp >= 0xFF) // if result_exp overflow
                return (bf16_t) {.bits = (result_sign << 15) | 0x7F80}; // return ±Inf depends on the sign
        }
    } else { // sing_a != sing_b
        if (mant_a >= mant_b) { // mant_a bigger than mant_b
            result_sign = sign_a; 
            result_mant = mant_a - mant_b; 
        } else { // mant_a < mant_b
            result_sign = sign_b;
            result_mant = mant_b - mant_a;
        }

        if (!result_mant)       // exact cancel → +0 (per BF16_ZERO macro)
            return BF16_ZERO(); // sing_a != sing_b && mant_b == mant_a
        
        // Normalize left until hidden 1 (bit7) is restored; decrement exponent
        while (!(result_mant & 0x80)) { 
            result_mant <<= 1; // mantissa left shift times 2
            if (--result_exp <= 0)  // exp - 1 
                return BF16_ZERO(); // if adjust to zero return BF16_ZERO()
        } 
    }

    return (bf16_t) {
        .bits = (result_sign << 15) | ((result_exp & 0xFF) << 7) |
                (result_mant & 0x7F),
    }; // Pack result
}

static inline bf16_t bf16_sub(bf16_t a, bf16_t b)
{
    b.bits ^= BF16_SIGN_MASK; // flip sign_b
    return bf16_add(a, b); 
}
```
:::
#### RISC-V
:::spoiler Assembly code
```
bf16_add:
    srli    t0, a0, 15            
    andi    t0, t0, 1             # t0 = sign_a (0/1)

    srli    t1, a1, 15            # t1 = sign_b (0/1)
    andi    t1, t1, 1

    srli    t2, a0, 7             
    andi    t2, t2, 0xFF          # t2 = exp_a (8-bit)
    srli    t3, a1, 7             
    andi    t3, t3, 0xFF          # t3 = exp_b (8-bit)

    andi    t4, a0, 0x7F          # t4 = mant_a (7-bit)
    andi    t5, a1, 0x7F          # t5 = mant_b (7-bit)
    
    li      t6, 0xFF
    bne     t2, t6, check_exp_b_FF
    beqz    t4, a_is_Inf
    
    mv      a0, a0
    ret                           # a is NaN
    
a_is_Inf:
    bne     t3, t6, ret_a_inf     # a is Inf b is not NaN or Inf
    bnez    t5, ret_b             # b is NaN return b
    bne     t0, t1, nan           # +Inf + -Inf = NaN

ret_b:
    mv      a0, a1
    ret                           # b is NaN

ret_a_inf:
    mv      a0, a0
    ret                           # a and b are same sign Inf

check_exp_b_FF:                   # a is not NaN or Inf
    bne     t3, t6, check_a_zero  # b is not NaN or Inf either
    mv      a0, a1                # b is NaN return NaN, b is Inf return Inf
    ret
    
check_a_zero:
    bnez    t2, check_b_zero      
    bnez    t4, check_b_zero      
    
    mv      a0, a1                # a is zero
    ret                           # return b
    
check_b_zero:
    bnez    t3, add_implict1
    bnez    t5, add_implict1      # a and b are not NaN Inf zero
    
    mv      a0, a0                # b is zero 
    ret                           # return a
add_implict1:
    beqz    t2, a_no_implict1
    ori     t4, t4, 0x80
     
a_no_implict1:
    beqz    t3, b_no_implict1
    ori     t5, t5, 0x80
b_no_implict1:
    
    sub     t6, t2, t3           # t6 <- exp_diff
    bgez    t6, exp_diff_ge0    
    
    mv      s0, t3              # s0 = result_exp <- exp_b
    li      s1, -8
    blt     t6, s1, ret_b       # a is too small
    
    neg     s1, t6              # s1 = -exp_diff
    srl     t4, t4, s1
    j       exp_aligned
    
exp_diff_ge0:
    beqz    t6, exp_equal
    mv      s0, t2              # s0 = result_exp <- exp_a
    li      s1, 8
    bgt     t6, s1, ret_a       # b is too small
    
    srl     t5, t5, t6
    j       exp_aligned
    
exp_equal:
    mv      s0, t2              # s0 = result_exp <- exp_a

exp_aligned:
    bne     t0, t1, diff_sign
    mv      s2, t0              # s2 = result_sign <- sign_a
    add     s3, t4, t5          # s3 = result_mant <- mant_a + mant_b
    
    andi    s5, s3, 0x100       # check is matissa overflow ?
    beqz    s5, pack            
    srli    s3, s3, 1
    addi    s0, s0, 1
    li      s4, 0xFF
    blt     s0, s4, pack        # check is exp overflow
    
    li      a0, 0x7F80          
    slli    s2, s2, 15
    or      a0, a0, s2
    ret                         # exp overflow return +-Inf
    
diff_sign:
    blt     t4, t5, b_bigger
    mv      s2, t0              # s2 = result_sign <- sign_a
    sub     s3, t4, t5          # s3 = result_mant <- mant_a - mant_b
    j       after_sub
    
b_bigger:
    mv      s2, t1              # s2 = result_sign <- sign_b
    sub     s3, t5, t4          # s3 = result_mant <- mant_b - mant_a
    
after_sub:
    beqz    s3, ret_zero
    
norm_loop:
    andi    s5, s3, 0x80
    bnez    s5, pack           # while (!(result_mant & 0x80))
    slli    s3, s3, 1
    addi    s0, s0, -1
    blez    s0, ret_zero
    j       norm_loop
    
pack:                          # s0 : result_exp s2 : result_sign s3 : result_mant
    andi    s3, s3, 0x7F
    andi    s0, s0, 0xFF
    slli    s0, s0, 7
    slli    s2, s2, 15
    or      a0, s0, s3
    or      a0, a0, s2
    ret
     
ret_a:
    mv      a0, a0
    ret
nan:
    li      a0, 0x7FC0           # NaN
    ret
    
ret_zero:
    mv      a0, x0               # zero
    ret
    
    
bf16_sub:
    li      t0, 0x8000
    xor     a1, a1, t0             # reverse b_sign
    j       bf16_add                              
```
:::
#### Execution info for RISC-V code

![螢幕擷取畫面 2025-10-13 201741](https://hackmd.io/_uploads/Syeu0w5agg.png)

#### Test Case
| Case | Operation | Input A (Hex) | Input A (value) | Input B (Hex) | Input B (value) | expect (Hex) | expect (value) | test type |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: 
| **TC1** | add | `0x0000` | $+0$ | `0x3FC0` | $+1.5$ | `0x3FC0` | $+1.5$ | normal |
| **TC2** | add | `0x7F80` | $+\infty$ | `0xFF80` | $-\infty$ | `NaN` | $NaN$ | edge |
| **TC3** | add | `0x3FC0` | $+1.5$ | `0x3F00` | $+0.5$ | `0x4000` | $+2.0$ | normal | 
| **TC4** | sub | `0x0000` | $+0$ | `0x3FC0` | $+1.5$ | `0xBFC0` | $-1.5$ | normal |
| **TC5** | sub | `0x7F80` | $+\infty$ | `0xFF80` | $-\infty$ | `0x7F80` | $+\infty$ | edge 
| **TC6** | sub | `0x3FC0` | $+1.5$ | `0x3F00` | $+0.5$ | `0x3F80` | $+1.0$ | normal |
### multiple
#### Note
- Special case: `a = Inf,b = NaN`
    - updated C handles this explicitly
  ```c
    // a = Inf or NaN
    if (exp_a == 0xFF) { 
        if (mant_a)            // a = NaN
            return a;          // a * b = NaN
        
        /*extra condition*/
        if (exp_b == 0xFF && mant_b) 
            return BF16_NAN(); // Inf * NaN = NaN 
        
        if (!exp_b && !mant_b) 
            return BF16_NAN(); // a * b = NaN
        
        return (bf16_t) {.bits = (result_sign << 15) | 0x7F80}; 
        // a = Inf, b = NaN will return here and the value is Inf 
        // but Inf * NaN = NaN
    } ```   
- **Exponent normalization when an operand’s exponent is zero**
    - Goal: Convert subnormal to normalized before multiply (if possible).
    - Example : 
        - Input:
        `exp_a = 0`, `mant_a = 0x10 (0001 0000)` 
        `exp_b = 130`, `mant_b = 0x40 (0100 0000)`
        - Process:
            - For `a` (subnormal): shift left until hidden-1 restored
→ shift 3 times: `mant_a = 0x80`, `exp_adjust = -3`, `set exp_a = 1`
            - For `b` (already normal): `exp_b = 130`, then restore hidden-1:
`mant_b |= 0x80` → `mant_b = 0xC0`
    - Meaning of `mant_a |= 0x80;`
    BF16 mantissa stores bits[6:0]. For normalized values we must insert the hidden 1 at bit7, so arithmetic uses 1.mantissa (not 0.mantissa) in:
    $(-1^{sign} \ × \ (1.mantissa) \ × \ 2^{(exp-bias)})$
  
- Post-multiply normalization when `result_mant >= 2`
    - Example 1 – no adjust
    `mant_a = 1.1000000b (1.5)` 
    `mant_b = 1.0010000b (1.125)`
    → `mant_a * mant_b = 1.6875 ≈ 1.1011b`(in `[1,2)`) → no shift.
    - Example 2 – needs adjust
    `mant_a = 1.1111111b (1.992)` 
    `mant_b = 1.1111111b (1.992)`
→ `mant_a * mant_b ≈ 3.968 ≈ 11.111b` → `result_mant > = 2` 
→ normalize:
`result_mant = result_mant >> 1` → `result_exp ++` 
This keeps the mantissa in the normalized range `[1, 2)` with the hidden-1 at bit7.

:::spoiler C Code
#### C Code
```c
static inline bf16_t bf16_mul(bf16_t a, bf16_t b)
{
    uint16_t sign_a = (a.bits >> 15) & 1;
    uint16_t sign_b = (b.bits >> 15) & 1; // get a, b sign bit
    int16_t exp_a = ((a.bits >> 7) & 0xFF);
    int16_t exp_b = ((b.bits >> 7) & 0xFF); // get a, b exp bits
    uint16_t mant_a = a.bits & 0x7F;
    uint16_t mant_b = b.bits & 0x7F; // get a, b mant bits

    uint16_t result_sign = sign_a ^ sign_b; // same sign : 0, different sign : 1

    if (exp_a == 0xFF) { // a = Inf or NaN
        if (mant_a) // a = NaN
            return a; // a * b = NaN
        // a = Inf
        if (exp_b == 0xFF && mant_b) // b = NaN
            return BF16_NAN(); // Inf * NaN = NaN 
        if (!exp_b && !mant_b) // a = Inf, b = 0
            return BF16_NAN();  // a * b = NaN
        return (bf16_t) {.bits = (result_sign << 15) | 0x7F80}; // return ±Inf depends on the sign
    }
    if (exp_b == 0xFF) {
        if (mant_b)
            return b;
        if (exp_a == 0xFF && mant_a) // a = NaN
            return BF16_NAN(); // Inf * NaN = NaN
        if (!exp_a && !mant_a)
            return BF16_NAN();
        return (bf16_t) {.bits = (result_sign << 15) | 0x7F80};
    } // same as above
    if ((!exp_a && !mant_a) || (!exp_b && !mant_b))
        return (bf16_t) {.bits = result_sign << 15}; // except for above condition, when a or b is zero return ±0 depends on the sign

    int16_t exp_adjust = 0;
    if (!exp_a) { // exp_a = 0
        while (!(mant_a & 0x80)) { // mant_a's MSB is not 1
            mant_a <<= 1;
            exp_adjust--;
        }
        exp_a = 1;
    } else
        mant_a |= 0x80; // 補上隱含的bit7，mantissa實際是bit6...0，但在計算時會補上bit7為1，也就是exp = 1讓他可以成為1.xxxx，方便計算
    if (!exp_b) {
        while (!(mant_b & 0x80)) {
            mant_b <<= 1;
            exp_adjust--; // if both a and b exp = 0 the exp_adjust will be accumulate (累積)
        }
        exp_b = 1;
    } else
        mant_b |= 0x80;

    uint32_t result_mant = (uint32_t) mant_a * mant_b; //both mant_a and mant_b are 1.XXXX and the exp part will compute by exp_adjust

    int32_t result_exp = (int32_t) exp_a + exp_b - BF16_EXP_BIAS + exp_adjust; // compute exp part 

    if (result_mant & 0x8000) { //當bit15是1，代表乘積超過2.0
        result_mant = (result_mant >> 8) & 0x7F; // result_mant基本是移7 bits讓結果是[6,...,0]，但是因為乘積超過2.0在多移1bit共8bits，把bit15對齊到bit7，除二讓他回到1.xxx
        result_exp++; // 然後把 result_exp ++ 相當於總數乘2
    } else
        result_mant = (result_mant >> 7) & 0x7F; // 剩下bit15是0,代表bit14是1,乘積不超過2.0直接移 7 bits，把bit14對齊到bit7

    if (result_exp >= 0xFF) //if result_exp overflow
        return (bf16_t) {.bits = (result_sign << 15) | 0x7F80}; // return ±Inf depends on the sign
    if (result_exp <= 0) {
        if (result_exp < -6) // 這個 -6 是 bf16 的 mantissa 能表示的極限
            return (bf16_t) {.bits = result_sign << 15}; // smaller than -6, return ±0 depends on the sign
        result_mant >>= (1 - result_exp); // 0~6之間 直接右移mantissa
        result_exp = 0; // result_exp 就可以是0
    }

    return (bf16_t) {.bits = (result_sign << 15) | ((result_exp & 0xFF) << 7) |
                             (result_mant & 0x7F)}; // combine all component and return
}
```
:::
#### RISC-V
:::spoiler Assembly code
```
.data
msg_pass:
    .asciz "tests pass\n"
msg_fail:
    .asciz "some tests fail\n"
    
.text
.globl main

main:
    li      s10, 0                 # fail_count = 0

    # ------------- case1 ---------------
    # TC1: 1.5 * 0.5 = 0.75
    # a = 0x3FC0 (1.5), b = 0x3F00 (0.5), exp = 0x3F40 (0.75)
    li      a0, 0x3FC0            # a = 1.5 (bf16)
    li      a1, 0x3F00            # b = 0.5 (bf16)
    jal     ra, bf16_mul
    li      t0, 0x3F40            # expected = 0.75 (bf16)
    bne     a0, t0, 1f            # if result != expected -> fail++
    j       2f
1:
    addi    s10, s10, 1
2:

    # ------------- case2 (edge) --------
    # TC2 (edge): +Inf * +0 = NaN
    # a = 0x7F80 (+Inf), b = 0x0000 (+0)
    li      a0, 0x7F80            # +Inf
    li      a1, 0x0000            # +0
    jal     ra, bf16_mul
    li      t1, 0x7F80
    and     t2, a0, t1            # �� exponent bits
    bne     t2, t1, 3f            # exp != 0xFF -> fail
    andi    t3, a0, 0x7F          # �� mantissa
    beqz    t3, 3f                # mant == 0 -> ���O NaN -> fail
    j       4f
3:
    addi    s10, s10, 1
4:

    # ------------- case3 ---------------
    # TC3: (-2.0) * (-0.5) = +1.0
    # a = 0xC000 (-2.0), b = 0xBF00 (-0.5), exp = 0x3F80 (+1.0)
    li      a0, 0xC000            # -2.0 (bf16)
    li      a1, 0xBF00            # -0.5 (bf16)
    jal     ra, bf16_mul
    li      t0, 0x3F80            # +1.0 (bf16)
    bne     a0, t0, 5f
    j       6f
5:
    addi    s10, s10, 1
6:


    # ----------------------------
    # print result
    # ----------------------------
    beqz    s10, print_pass       # fail_count == 0 �� pass
    la      a0, msg_fail
    li      a7, 4                 # print_string
    ecall
    j       exit

print_pass:
    la      a0, msg_pass
    li      a7, 4                 # print_string
    ecall

exit:
    li      a7, 10                # exit
    ecall

    
bf16_mul:
    srli    t0, a0, 15            
    andi    t0, t0, 1             # t0 = sign_a (0/1)

    srli    t1, a1, 15            # t1 = sign_b (0/1)
    andi    t1, t1, 1

    srli    t2, a0, 7             
    andi    t2, t2, 0xFF          # t2 = exp_a (8-bit)
    srli    t3, a1, 7             
    andi    t3, t3, 0xFF          # t3 = exp_b (8-bit)

    andi    t4, a0, 0x7F          # t4 = mant_a (7-bit)
    andi    t5, a1, 0x7F          # t5 = mant_b (7-bit)
    
    xor     s0, t0, t1            # s0 = result_sign
    
    li      t6, 0xFF
    bne     t2, t6, check_b_FF    # exp_a != 0xFF ?
    bnez    t4, nan               # exp_a == FF mant_a != 0 => NaN
    
check_b_FF:
    bne     t3, t6, both_not_nan   # exp_b != 0xFF ?
    bnez    t5, nan               # exp_b == FF mant_b != 0 => NaN
    
both_not_nan:
    beq     t2, t6, a_is_inf
    j       check_b_inf

a_is_inf:  
    beqz    t3, b_is_zero
    j       check_b_inf
    
b_is_zero:   
    beqz    t5, nan                # a = Inf b = 0 return nan
check_b_inf:
    bne     t3, t6, check_inf            
    beqz    t2, b_is_inf
    j       check_inf
b_is_inf:
    beqz    t4, nan                # b = Inf a = 0 return nan
check_inf:
    beq     t2, t6, inf            # b != Inf a == Inf ret Inf
    beq     t3, t6, inf            # a != Inf b == Inf ret Inf
    
    beqz    t2, check_mant_a_zero
    j       check_exp_b_zero
    
check_mant_a_zero:  
    beqz    t4, zero
    
check_exp_b_zero:
    beqz    t3, check_mant_b_zero
    j       exp_adjust
    
check_mant_b_zero:
    beqz    t5, zero

exp_adjust:
    li      a2, 0
    
    beqz    t2, while_a
    li      s7, 0x80
    or      t4, t4, s7
    j       adjust_b
while_a:                               # while (!(mant_a & 0x80)) { mant_a <<=1; exp_adjust--; }
    andi    a4, t4, 0x80               # (mant_a & 0x80)
    bnez    a4, done_adjust_a
    slli    t4, t4, 1
    addi    a2, a2, -1
    j       while_a
    
done_adjust_a:
    li      t2, 1
    
adjust_b:
    beqz    t3, while_b
    ori     t5, t5, 0x80
    j       adjust_done
    
while_b:
    andi    a4, t5, 0x80
    bnez    a4, done_adjust_b
    slli    t5, t5, 1
    addi    a2, a2, -1
    j       while_b
    
done_adjust_b:
    li      t3, 1

adjust_done:
    li      a3, 0
    li      a4, 8                       # counter
    
mul_loop:                               # mant_a * mant_b
    andi    a5, t5, 1
    beqz    a5, skip_add
    add     a3, a3, t4
    
skip_add:
    slli    t4, t4, 1
    srli    t5, t5, 1
    addi    a4, a4, -1
    bnez    a4, mul_loop

    # result_exp = exp_a + exp_b - 127 + exp_adjust
    add     a4, t2, t3            # exp_a + exp_b
    add     a4, a4, a2            # + exp_adjust
    addi    a4, a4, -127          # BF16_EXP_BIAS
    
    li      a5, 0x8000
    and     a5, a3, a5
    beqz    a5, no_big
    srli    a3, a3, 8
    andi    a3, a3, 0x7F
    addi    a4, a4, 1
    j       scaled
    
no_big:
    srli    a3, a3, 7
    andi    a3, a3, 0x7F
    
scaled:
    li      a5, 0xFF
    ble     a5, a4, inf
    
    blez    a4, underflow
    
    slli    t0, s0, 15            # sign
    andi    t1, a4, 0xFF
    slli    t1, t1, 7             # exp<<7
    or      t0, t0, t1
    or      t0, t0, a3            # + mant
    mv      a0, t0
    ret
inf:
    slli    a0, s0, 15
    li      s8, 0x7F80
    or      a0, a0, s8
    ret

zero:
    slli    a0, s0, 15            # 0
    ret

nan:
    li      a0, 0x7FC0           # NaN
    ret
    
underflow:
    li      a5, -6
    blt     a4, a5, zero
    
    li      a5, 1
    sub     a5, a5, a4

shift_dn:
    beqz    a5, pack_den
    srli    a3, a3, 1
    addi    a5, a5, -1
    j       shift_dn
pack_den:
    andi    a3, a3, 0x7F
    slli    t0, t6, 15            # sign
    or      t0, t0, a3            # exp=0
    andi    a0, t0, 0xFF
    ret
```
:::
#### Test Case
| Case | Operation | Input A (Hex) | Input A (value) | Input B (Hex) | Input B (value) | expect (Hex) | expect (value) | test type |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: 
| **TC1** | mul | `0x3FC0` | $+1.5$ | `0x3F00` | $+0.5$ | `0x3F40` | $+0.75$ | normal |
| **TC2** | mul | `0x7F80` | $+\infty$ | `0x0000` | $0$ | `NaN` | $NaN$ | edge |
| **TC3** | mul | `0xC000` | $-2.0$ | `0xBF00` | $-0.5$ | `0x3F80` | $+1.0$ | normal |

#### Execution info for RISC-V code

![螢幕擷取畫面 2025-10-13 232243](https://hackmd.io/_uploads/BJ5ttc5all.png)

### divide
#### Note
-  **Meaning of `if (!exp_a) result_exp--;`**
   When performing floating-point multiply/divide operations in hardware, the mantissa is assumed to be in normalized form $1.xxx$
   To simplify the arithmetic, subnormal numbers (where $exp = 0$)are temporarily treated as if normalized by setting $exp = 1$
After the computation, we must adjust the exponent back to correct for this artificial bias.
`if dividend(a)`was subnormal : `result_exp --` (divide by 2)
`if divisor(b)`was subnormal : `result_exp ++` (multiply by 2)
#### C code

:::spoiler C code
```c
static inline bf16_t bf16_div(bf16_t a, bf16_t b)
{
    uint16_t sign_a = (a.bits >> 15) & 1;
    uint16_t sign_b = (b.bits >> 15) & 1; // get a, b sign bit
    int16_t exp_a = ((a.bits >> 7) & 0xFF);
    int16_t exp_b = ((b.bits >> 7) & 0xFF); // get a, b exp bits
    uint16_t mant_a = a.bits & 0x7F;
    uint16_t mant_b = b.bits & 0x7F; // get a, b mant bits

    uint16_t result_sign = sign_a ^ sign_b; // same sign : 0, different sign : 1

    if (exp_b == 0xFF) {
        if (mant_b)
            return b; // b is NaN, anything / NaN = NaN
        /* Inf/Inf = NaN */
        if (exp_a == 0xFF && !mant_a)
            return BF16_NAN(); // a and b are Inf,  Inf / Inf = NaN 
        return (bf16_t) {.bits = result_sign << 15}; // b = Inf, anything / ±Inf = ±0 return ±0 depends on the result_sign
    }
    if (!exp_b && !mant_b) { // if b = ±0
        if (!exp_a && !mant_a) // if a = ±0
            return BF16_NAN(); // 0 / 0 = NaN
        return (bf16_t) {.bits = (result_sign << 15) | 0x7F80}; // b = ±0 a != 0 a / ±0 = ±Inf 
    }
    if (exp_a == 0xFF) {
        if (mant_a) 
            return a; // a is NaN, NaN / anything = NaN
        return (bf16_t) {.bits = (result_sign << 15) | 0x7F80}; // a is ±Inf
    }
    if (!exp_a && !mant_a)
        return (bf16_t) {.bits = result_sign << 15}; // a is 0 ，except above condition a / b = 0

    if (exp_a)
        mant_a |= 0x80; // let mantissa's bit 7 be 1 to ensure the computing
    if (exp_b)
        mant_b |= 0x80; // same as above

    uint32_t dividend = (uint32_t) mant_a << 15; // dividend(被除數) extend to 32bits mant_a[7...0] move to [22...15] 
    uint32_t divisor = mant_b; // divisor(除數)
    uint32_t quotient = 0;

    for (int i = 0; i < 16; i++) {
        quotient <<= 1; // left shift to get a empty LSB
        if (dividend >= (divisor << (15 - i))) { //divisor align to dividend and check is dividend bigger than divisor, if not go to next iteration
            dividend -= (divisor << (15 - i)); // if yes "dividend - divisor"
            quotient |= 1; // and let the quotient set to 1
        }
    }

    int32_t result_exp = (int32_t) exp_a - exp_b + BF16_EXP_BIAS;

    if (!exp_a) 
        result_exp--;
    if (!exp_b)
        result_exp++; // explain at Note

    if (quotient & 0x8000) // if 商's MSB is at bit15 -> >=2
        quotient >>= 8; // right shift 7+1bits to align bit15 to bit7
    else { //商's MSB is at bit14 -> < 2
        while (!(quotient & 0x8000) && result_exp > 1) {
            quotient <<= 1;  
            result_exp--;
        } // first align to bit15 to get the exact result_exp
        quotient >>= 8; // right shift 7+1bits to align bit15 to bit7
    }
    quotient &= 0x7F; //extract last 7 bits [bit6..bit0]

    if (result_exp >= 0xFF)
        return (bf16_t) {.bits = (result_sign << 15) | 0x7F80}; // ±Inf
    if (result_exp <= 0)
        return (bf16_t) {.bits = result_sign << 15};// ±0
    return (bf16_t) {.bits = (result_sign << 15) | ((result_exp & 0xFF) << 7) |
                             (quotient & 0x7F)};
} //combine all component
```
:::

#### RISC-V
:::spoiler RISV-V
```
bf16_div:
    srli    t0, a0, 15
    andi    t0, t0, 1

    srli    t1, a1, 15
    andi    t1, t1, 1

    srli    t2, a0, 7
    andi    t2, t2, 0xFF

    srli    t3, a1, 7
    andi    t3, t3, 0xFF

    andi    t4, a0, 0x7F
    andi    t5, a1, 0x7F

    xor     a2, t0, t1
    
check_b_FF:
    addi    t6, x0, 255
    bne     t3, t6, check_b_zerocase

    bne     t5, x0, ret_b
    bne     t2, t6, zero
    beq     t4, x0, nan
    jal     x0, zero

check_b_zerocase:
    bne     t3, x0, check_a_FF
    bne     t5, x0, check_a_FF
    bne     t2, x0, inf
    beq     t4, x0, nan
    jal     x0, inf

check_a_FF:
    bne     t2, t6, check_a_zerocase
    bne     t4, x0, ret_a
    jal     x0, inf

check_a_zerocase:
    bne     t2, x0, implict_1_a
    beq     t4, x0, zero

implict_1_a:
    beq     t2, x0, implict_1_b
    ori     t4, t4, 0x80
    
implict_1_b:
    beq     t3, x0, start_divide
    ori     t5, t5, 0x80
    
start_divide:
    slli    a4, t4, 15
    addi    a5, x0, 0

    slli    t6, t5, 15

    addi    t0, x0, 16

div_loop:
    slli    a5, a5, 1

    sltu    t1, a4, t6
    bne     t1, x0, no_sub
    sub     a4, a4, t6
    ori     a5, a5, 1   
no_sub:
    srli    t6, t6, 1
    addi    t0, t0, -1
    bne     t0, x0, div_loop
    
result_exp:
    sub     a3, t2, t3
    addi    a3, a3, 127

    beq     t2, x0, dec_exp_a
    jal     x0, chk_exp_b
dec_exp_a:
    addi    a3, a3, -1

chk_exp_b:
    beq     t3, x0, inc_exp_b
    jal     x0, norm_q
inc_exp_b:
    addi    a3, a3, 1

norm_q:
    lui     t0, 0x8
    and     t1, a5, t0
    beq     t1, x0, shift_left_phase

    srli    a5, a5, 8
    jal     x0, combine_all_component

shift_left_phase:
norm_loop:
    and     t1, a5, t0
    bne     t1, x0, after_left_norm
    addi    t1, x0, 1
    slt     t1, t1, a3
    beq     t1, x0, after_left_norm
    slli    a5, a5, 1
    addi    a3, a3, -1
    jal     x0, norm_loop

after_left_norm:
    srli    a5, a5, 8

combine_all_component:
    andi    a5, a5, 0x7F

    addi    t0, x0, 255
    bge     a3, t0, inf

    beq     a3, x0, zero
    slt     t1, a3, x0
    bne     t1, x0, zero

    andi    t0, a3, 255
    slli    t0, t0, 7
    slli    a0, a2, 15
    or      a0, a0, t0
    or      a0, a0, a5
    jalr    x0, ra, 0


ret_a:
    addi    a0, a0, 0
    jalr    x0, ra, 0

ret_b:
    addi    a0, a1, 0
    jalr    x0, ra, 0

inf:
    slli    a0, a2, 15
    addi    t0, x0, 255
    slli    t0, t0, 7
    or      a0, a0, t0
    jalr    x0, ra, 0

nan:
    addi    t0, x0, 255
    slli    t0, t0, 7
    ori     a0, t0, 0x40
    jalr    x0, ra, 0

zero:
    slli    a0, a2, 15
    jalr    x0, ra, 0
```
:::
#### Test Case
| Case | Operation | Input A (Hex) | Input A (value) | Input B (Hex) | Input B (value) | expect (Hex) | expect (value) | test type |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: 
| **TC1** | div | `0x3FC0` | $+1.5$ | `0x3F00` | $+0.5$ | `0x4040` | $+3.0$ | normal |
| **TC2** | div | `0x4000` | $+2.0$ | `0x0000` | $+0$ | `Inf` | $Inf$ | edge |
| **TC3** | div | `0xC000` | $-2.0$ | `0xBF00` | $-0.5$ | `0x4080` | $+4.0$ | normal |

#### Execution info for RISC-V code

![image](https://hackmd.io/_uploads/rk1I9Axkbe.png)


### square root 
#### Note 
-   binary search 那邊概念還不是很通，先寫risc-v，寫完再回頭看
#### C Code

:::spoiler C code
```c
static inline bf16_t bf16_sqrt(bf16_t a)
{
    uint16_t sign = (a.bits >> 15) & 1; // get "a" sign bit
    int16_t exp = ((a.bits >> 7) & 0xFF); // get "a" exp bits
    uint16_t mant = a.bits & 0x7F; // get "a" mantissa bits

    /* Handle special cases */
    if (exp == 0xFF) {
        if (mant) //a is NaN
            return a; /* NaN propagation */
        if (sign) //a is -Inf
            return BF16_NAN();  // sqrt(-Inf) = NaN 
        return a;               // sqrt(+Inf) = +Inf
    }

    if (!exp && !mant) // a is 0
        return BF16_ZERO(); // sqrt(0) = 0

    if (sign) // a is negative value
        return BF16_NAN(); // sqrt(negative val) = NaN

    
    if (!exp) // value that is too small or it's subnormal value
        return BF16_ZERO(); // return zero

    /* Direct bit manipulation square root algorithm */
    /* For sqrt: new_exp = (old_exp - bias) / 2 + bias */
    int32_t e = exp - BF16_EXP_BIAS; // get the real exponent e without bias 
    int32_t new_exp; 
    
    /* Get full mantissa with implicit 1 */
    uint32_t m = 0x80 | mant;  // let subnormal value get implicit 1 that can be well compute
    /* Range [128, 256) representing [1.0, 2.0), using interger to represent decimal (小數) */
    
    /* Adjust for odd exponents: sqrt(2^odd * m) = 2^((odd-1)/2) * sqrt(2*m) */
    if (e & 1) {  
        m <<= 1;  // Double mantissa for odd exponentadjust odd exp into 
        new_exp = ((e - 1) >> 1) + BF16_EXP_BIAS; // e  -> (e-1) / 2
    } else {
        new_exp = (e >> 1) + BF16_EXP_BIAS;// e -> e / 2
    }
    
    /* Now m is in range [128, 256) or [256, 512) if exponent was odd */
    /* Binary search for integer square root */
    /* We want result where result^2 = m * 128 (since 128 represents 1.0) */
    
    uint32_t low = 90;          /* Min sqrt (roughly sqrt(128)) */
    uint32_t high = 256;        /* Max sqrt (roughly sqrt(512)) */
    uint32_t result = 128;      /* Default */
    
    /* Binary search for square root of m */
    while (low <= high) {
        uint32_t mid = (low + high) >> 1; 
        uint32_t sq = (mid * mid) / 128;  /* Square and scale */
        
        if (sq <= m) {
            result = mid;  /* This could be our answer */
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }
    
    /* result now contains sqrt(m) * sqrt(128) / sqrt(128) = sqrt(m) */
    /* But we need to adjust the scale */
    /* Since m is scaled where 128=1.0, result should also be scaled same way */
    
    /* Normalize to ensure result is in [128, 256) */
    if (result >= 256) {
        result >>= 1; 
        new_exp++; // result bigger the 256, result divided by 2 and exp + 1
    } else if (result < 128) {
        while (result < 128 && new_exp > 1) {
            result <<= 1;
            new_exp--; // result smaller than 128, result multiple by 2 and exp - 1
        }
    }
    
    /* Extract 7-bit mantissa (remove implicit 1) */
    uint16_t new_mant = result & 0x7F;
    
    /* Check for overflow/underflow */
    if (new_exp >= 0xFF)
        return (bf16_t) {.bits = 0x7F80};  // +Inf 
    if (new_exp <= 0)
        return BF16_ZERO(); // 0
    
    return (bf16_t) {.bits = ((new_exp & 0xFF) << 7) | new_mant};
} // combine all component
```
:::
#### RISC-V
:::spoiler RISV-V
```
bf16_sqrt:
    srli    t0, a0, 15
    andi    t0, t0, 1      # sign

    srli    t1, a0, 7
    andi    t1, t1, 0xFF   # exp

    andi    t2, a0, 0x7F   # mant
    
check_FF:
    addi    t3, x0, 0xFF
    bne     t1, t3, check_zerocase

    bne     t2, x0, nan
    bne     t0, x0, nan
    jal     x0, inf

check_zerocase:
    bne     t1, x0, check_negative
    bne     t2, x0, check_negative
    jal     x0, zero

check_negative:
    beq     t0, x0, check_too_small
    jal     x0, nan

check_too_small:
    beq     t1, x0, zero

real_exp:
    addi    t1, t1, -127    # e 取代 exp
    li      t4, 0           # new_exp
    
implict_1:
    ori     t2, t2, 0x80    # m 取代 mant
      
start_square_root:
    andi    t6, t1, 1
    sll     t2, t2, t6 # mant <<= mask
    sub     t4, t1, t6 # new_exp = e - mask
    srai    t4, t4, 1  # new_exp >>= 1 new_exp is int32_t so need use arithmetic shift
    addi    t4, t4, 127 # new_exp += BF16_EXP_BIAS

binary_search_init:
    li      a1, 90          # low
    li      a2, 255         # high
    li      a3, 128         # result
    
binary_search_loop:
     bgt    a1, a2, binary_loop_end
     add    a4, a1, a2      
     srli   a4, a4, 1       # mid

mul_loop_init:  
     li     a5, 0           # sq
     li     s0, 0           # i
     li     s1, 32
     
mul_loop:
    bge     s0, s1, mul_loop_end
    srl     t6, a4, s0      # mask
    andi    t6, t6, 1 
    sub     t6, x0, t6  
    sll     s3, a4, s0     
    and     s3, s3, t6  
    add     a5, a5, s3  
    addi    s0, s0, 1       # i++
    j       mul_loop
    
mul_loop_end:
    srli    a5, a5, 7
    ble     a5, t2, set_result
    addi    a2, a4, -1
    j       binary_search_loop
    
set_result:
    mv      a3, a4
    addi    a1, a4, 1
    j       binary_search_loop
    
binary_loop_end:
    li      s6, 0xFF
    andi    t2, a3, 0x7F
    blt     t4, s6, chk_new_exp
    jal     x0, inf
    
chk_new_exp:
    bgt     t4, x0, return
    jal     x0, zero
    
return:
    andi    t4, t4, 0xFF
    slli    t4, t4, 7
    or      a0, t4, t2
    ret
    
inf:
    slli    a0, t0, 15
    addi    t0, x0, 255
    slli    t0, t0, 7
    or      a0, a0, t0
    jalr    x0, ra, 0

nan:                       # 0x7FC0
    li      t0, 0          # sign = 0
    addi    t1, x0, 255
    slli    t1, t1, 7
    ori     a0, t1, 0x40   # mant = 0x40
    jalr    x0, ra, 0

zero:                      # 0x0000
    mv      a0, x0
    jalr    x0, ra, 0
```
:::
#### Test Case
| Case | Operation | Input A (Hex) | Input A (value) | expect (Hex) | expect (value) | test type |
| :---:   |:---: | :---:    | :---:  | :---:    | :---:  | :---:  |
| **TC1** | sqrt | `0x4080` | $+4.0$ | `0x4000` | $+2.0$ | normal |
| **TC2** | sqrt | `0xBF80` | $-1.0$ | `0x7F80` | $NaN$  | edge   |
| **TC3** | dqrt | `0x7F80` | $+Inf$ | `0x7F80` | $+Inf$ | edge   |

#### Execution info for RISC-V code
![螢幕擷取畫面 2025-10-30 202348](https://hackmd.io/_uploads/SkiZq0gkWe.png)



### Reference
https://hackmd.io/@3xOSPTI6QMGdj6jgMMe08w/Bk-uxCYxz
https://zhuanlan.zhihu.com/p/540887151

## Five Stage

| Stage   | Name               | Function Summary                           | Where to Observe in Ripes                        |
| ------- | ------------------ | ------------------------------------------ | ------------------------------------------------ |
| 1️⃣ IF  | Instruction Fetch  | Fetch the instruction from memory          | **Instruction Memory**, **Program Counter (PC)** |
| 2️⃣ ID  | Instruction Decode | Decode the instruction and read registers  | **Register File**                                |
| 3️⃣ EX  | Execute / ALU      | Perform arithmetic or calculate address    | **ALU**, **MUX (ALUSrc)**                        |
| 4️⃣ MEM | Memory Access      | Access data memory (load/store)            | **Data Memory**                                  |
| 5️⃣ WB  | Write Back         | Write the result back to the register file | **Register File (Write Enable)**                 |

### `li   s10, 10`
#### Instruction Function
This is a pseudo-instruction, which the assembler expands into the real RISC-V instruction:`addi s10, x0, 10`
#### IF(Instruction Fetch)
![image](https://hackmd.io/_uploads/Skwe3nZ1bg.png)
- The PC (Program Counter) starts at address `0x00000000`.
- The Instruction Memory outputs the 32-bit instruction stored at that address, 0x00a00d13, which corresponds to addi `s10, x0, 10`.
#### ID(Instruction Decode)
![image](https://hackmd.io/_uploads/HywNh3b1Zl.png)
- The Decoder interprets the instruction 0x00a00d13 and splits it into its fields:
```
opcode = 0010011 (I-type, addi)
funct3 = 000
rd  = s10 (x26)
rs1 = x0
imm = 10
```
- Reg1 (x0) reads the value `0x00000000`, since register x0 is always hardwired to zero.
- Because this is an I-type instruction, there is no Reg2 input; the second ALU operand comes from the immediate value instead. Thus, Reg2 shows the default value `0x00000000`.
- The Immediate Generator outputs the immediate value 10, represented as `0x0000000a`.
#### EX(Execute / ALU)
![image](https://hackmd.io/_uploads/ryThyaWJ-l.png)
- The ALU performs the operation `Reg[x0] + imm` → `0 + 10 = 10`.
- Since this is an `addi` instruction, the MUX (Multiplexer) selects the immediate (imm) as the second ALU input instead of a register.
- The Result (Res) output is `0x0000000a`, indicating the ALU computed the correct result.

#### MEM(Memory Access)
![image](https://hackmd.io/_uploads/Skjgea-kbx.png)
- This instruction does not access the data memory because it is neither a load nor a store instruction.
- The ALU result simply passes through to the next stage without modifying memory.

#### WB(Write Back)
![image](https://hackmd.io/_uploads/ryVgbpZybg.png)
- The ALU result (10, or `0x0000000a`) is written back to the destination register `rd = s10` (x26, register index `0x1a`).
- After execution, you can confirm that s10 now contains the value `0x0000000a`.
![image](https://hackmd.io/_uploads/Hk5sbp-Jbx.png)
