---
# System prepended metadata

title: 'Assignment1: RISC-V Assembly and Instruction Pipeline'

---

# Assignment1: RISC-V Assembly and Instruction Pipeline
contributed by <`戴仁杰`>
repo: https://github.com/dozingmoon/ca2025-quizzes
Disclaimer: I used AI tools such as ChatGPT for better understanding of algorithm or architecture descriptions.
# Problem B
## C Code
:::spoiler `q1/q1-uf8.c`
```clike=
#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;
}

```
:::


## Assembly
:::spoiler `hw1-uf8.s`
```asm=
.data
str1:  .string ": produces value "
str2:  .string " but encodes back to "
str3:  .string ": value "
str4:  .string " >= previous_value "
strnl: .string "\n"
strps: .string "All tests passed"

.text
#################
#     main      #
#################
main:
  addi    sp,sp,-4
  sw      ra,0(sp)
  jal     test    # a1: passed
  beq     a1,x0,NotPassed
  # Test is passed
  li a7,4
  la a0, strps
  ecall
  li      a5,0  
  j       exit_main
NotPassed:
  li      a5,1
  ret
exit_main:
  mv      a0,a5
  lw      ra,0(sp)
  addi    sp,sp,4

  li a7, 10      # exit system call
  ecall          

#################
#      CLZ      #
#################
clz:             # x: a0, return: a1
  li t1,32       # t1 = n = 32
  li t2,16       # t2 = c = 16
clz_loop:
  srl t3,a0,t2   # t3=y=x>>c
  beq t3,x0,clz_else
  sub t1,t1,t2
  mv t0,t3
clz_else:
  srai t2,t2,1
  beq t2,x0,clz_loop
  sub a1,t1,t0
  ret

#################
#  uf8_decode   #
#################
uf8_decode:       # fl: a0, return: a1
  andi t0,a0,0x0f   # mantissa
  srli t1,a0,4      # exponent
  
  li t2,15
  sub t2,t2,t1
  li t3,0x7fff
  srl t2,t3,t2
  slli t2,t2,4      # offset
  sll a0,t0,t1
  add a1,a0,t2
  ret
  
#################
#  uf8_encode   #
#################
uf8_encode: # value: a0, ret: a1
  srli t0,a0,4      # will be zero if a0 < 16
  bne t0,x0,ValGe16
  ret
ValGe16:
  addi sp,sp,-4
  sw ra,0(sp)
  jal clz           # lz: a1
  lw ra,0(sp)
  addi sp,sp,4
  
  li t0, 31
  sub t0,t0,a1      # msb: t0
  
  li t1,0           # exp: t1
  li t2,0           # ovf: t2
  
  addi t3,t0,-5
  bltz t3, ExpLoopCond
  
  # if msb >= 5
  # estimate exponent
  addi t1,t0,-4
  li t3,15
  ble t1,t3,ExpLe15
  mv t1,t3
ExpLe15:
  li t3,0
OvfLoopCond:
  bge t3,t1, AdjLoopCond
  slli t2,t2,1
  addi t2,t2,16
  addi t3,t3,1
  j OvfLoopCond
AdjLoopCond:
  sltu t3,a0,t2 # t3: value < overflow
  sltu t4,x0,t1 # t4: 0 < exponent
  and t3,t3,t4
  bgeu x0,t3,ExpLoopCond # break when cond is 0
  addi t2,t2,-16
  srli t2,t2,1
  addi t1,t1,-1
  j AdjLoopCond
ExpLoopCond:
  li t3,15
  bgeu t1,t3,ExpLoopExit
  slli t3,t2,1
  addi t3,t3,16 # t3: next_overflow
  bltu a0,t3,ExpLoopExit
  mv t2,t3
  addi t1,t1,1
  j ExpLoopCond
ExpLoopExit:
  sub t3,a0,t2
  srl t3,t3,t1
  slli t1,t1,4
  or a1,t1,t3
  ret

#################
#     test      #
#################
test:
  li t5,0xffffffff # t0: previous_value
  li t6, 1 # t1: passed
  
  li s0, 255 # s0: fl/i
TestLoopCond:
  blt s0,x0,TestLoopExit
  addi sp,sp,-4
  sw ra,0(sp)
  mv a0,s0
  jal uf8_decode # a1: value
  mv a0,a1
  jal uf8_encode 
  mv s1, a0 # s1: value; a1: fl2
  lw ra,0(sp)
  addi sp,sp,4
  
  beq s0,a1,fl_equal_fl2
  
  # Print if fl != fl2
  li a7, 34 # Print hex
  mv a0,s0 # fl
  ecall
  
  li a7,4
  la a0,str1
  ecall
  
  li a7,34
  mv a0,s1 # value
  ecall
  
  li a7,4
  la a0,str2
  ecall
  
  li a7,34
  mv a0,a1 # fl2
  ecall
  
  li a7,4
  la a0,strnl
  ecall
  mv t6,x0
fl_equal_fl2:
  bgtu t5,s1,prev_gt_val
  # Print if previous_value <= value
  li a7, 34 # Print hex
  mv a0,s0 # fl
  ecall
  
  li a7,4
  la a0,str3
  ecall
  
  li a7,34
  mv a0,s1 # value
  ecall
  
  li a7,4
  la a0,str4
  ecall
  
  li a7,34
  mv a0,t5 # previous_value
  ecall
  
  li a7,4
  la a0,strnl
  ecall
  
  mv t6,x0
prev_gt_val:
  mv t5,s1
  addi s0,s0,-1
  j TestLoopCond
TestLoopExit:
  mv a1,t6
  ret
```

:::
# Problem C 
## C Code
:::spoiler `q1/q1-bf16.c`
```clike=
#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})
#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);
}

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);
}

static inline bf16_t f32_to_bf16(float val)
{
    uint32_t f32bits;
    memcpy(&f32bits, &val, sizeof(float));
    if (((f32bits >> 23) & 0xFF) == 0xFF)
        return (bf16_t) {.bits = (f32bits >> 16) & 0xFFFF};
    f32bits += ((f32bits >> 16) & 1) + 0x7FFF;
    return (bf16_t) {.bits = f32bits >> 16};
}

static inline float bf16_to_f32(bf16_t val)
{
    uint32_t f32bits = ((uint32_t) val.bits) << 16;
    float result;
    memcpy(&result, &f32bits, sizeof(float));
    return result;
}

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;
    int16_t exp_a = ((a.bits >> 7) & 0xFF);
    int16_t exp_b = ((b.bits >> 7) & 0xFF);
    uint16_t mant_a = a.bits & 0x7F;
    uint16_t mant_b = b.bits & 0x7F;

    if (exp_a == 0xFF) {
        if (mant_a)
            return a;
        if (exp_b == 0xFF)
            return (mant_b || sign_a == sign_b) ? b : BF16_NAN();
        return a;
    }
    if (exp_b == 0xFF)
        return b;
    if (!exp_a && !mant_a)
        return b;
    if (!exp_b && !mant_b)
        return a;
    if (exp_a)
        mant_a |= 0x80;
    if (exp_b)
        mant_b |= 0x80;

    int16_t exp_diff = exp_a - exp_b;
    uint16_t result_sign;
    int16_t result_exp;
    uint32_t result_mant;

    if (exp_diff > 0) {
        result_exp = exp_a;
        if (exp_diff > 8)
            return a;
        mant_b >>= exp_diff;
    } else if (exp_diff < 0) {
        result_exp = exp_b;
        if (exp_diff < -8)
            return b;
        mant_a >>= -exp_diff;
    } else {
        result_exp = exp_a;
    }

    if (sign_a == sign_b) {
        result_sign = sign_a;
        result_mant = (uint32_t) mant_a + mant_b;

        if (result_mant & 0x100) {
            result_mant >>= 1;
            if (++result_exp >= 0xFF)
                return (bf16_t) {.bits = (result_sign << 15) | 0x7F80};
        }
    } else {
        if (mant_a >= mant_b) {
            result_sign = sign_a;
            result_mant = mant_a - mant_b;
        } else {
            result_sign = sign_b;
            result_mant = mant_b - mant_a;
        }

        if (!result_mant)
            return BF16_ZERO();
        while (!(result_mant & 0x80)) {
            result_mant <<= 1;
            if (--result_exp <= 0)
                return BF16_ZERO();
        }
    }

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

static inline bf16_t bf16_sub(bf16_t a, bf16_t b)
{
    b.bits ^= BF16_SIGN_MASK;
    return bf16_add(a, b);
}

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;
    int16_t exp_a = ((a.bits >> 7) & 0xFF);
    int16_t exp_b = ((b.bits >> 7) & 0xFF);
    uint16_t mant_a = a.bits & 0x7F;
    uint16_t mant_b = b.bits & 0x7F;

    uint16_t result_sign = sign_a ^ sign_b;

    if (exp_a == 0xFF) {
        if (mant_a)
            return a;
        if (!exp_b && !mant_b)
            return BF16_NAN();
        return (bf16_t) {.bits = (result_sign << 15) | 0x7F80};
    }
    if (exp_b == 0xFF) {
        if (mant_b)
            return b;
        if (!exp_a && !mant_a)
            return BF16_NAN();
        return (bf16_t) {.bits = (result_sign << 15) | 0x7F80};
    }
    if ((!exp_a && !mant_a) || (!exp_b && !mant_b))
        return (bf16_t) {.bits = result_sign << 15};

    int16_t exp_adjust = 0;
    if (!exp_a) {
        while (!(mant_a & 0x80)) {
            mant_a <<= 1;
            exp_adjust--;
        }
        exp_a = 1;
    } else
        mant_a |= 0x80;
    if (!exp_b) {
        while (!(mant_b & 0x80)) {
            mant_b <<= 1;
            exp_adjust--;
        }
        exp_b = 1;
    } else
        mant_b |= 0x80;

    uint32_t result_mant = (uint32_t) mant_a * mant_b;

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

    if (result_mant & 0x8000) {
        result_mant = (result_mant >> 8) & 0x7F;
        result_exp++;
    } else
        result_mant = (result_mant >> 7) & 0x7F;

    if (result_exp >= 0xFF)
        return (bf16_t) {.bits = (result_sign << 15) | 0x7F80};
    if (result_exp <= 0) {
        if (result_exp < -6)
            return (bf16_t) {.bits = result_sign << 15};
        result_mant >>= (1 - result_exp);
        result_exp = 0;
    }

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

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;
    int16_t exp_a = ((a.bits >> 7) & 0xFF);
    int16_t exp_b = ((b.bits >> 7) & 0xFF);
    uint16_t mant_a = a.bits & 0x7F;
    uint16_t mant_b = b.bits & 0x7F;

    uint16_t result_sign = sign_a ^ sign_b;

    if (exp_b == 0xFF) {
        if (mant_b)
            return b;
        /* Inf/Inf = NaN */
        if (exp_a == 0xFF && !mant_a)
            return BF16_NAN();
        return (bf16_t) {.bits = result_sign << 15};
    }
    if (!exp_b && !mant_b) {
        if (!exp_a && !mant_a)
            return BF16_NAN();
        return (bf16_t) {.bits = (result_sign << 15) | 0x7F80};
    }
    if (exp_a == 0xFF) {
        if (mant_a)
            return a;
        return (bf16_t) {.bits = (result_sign << 15) | 0x7F80};
    }
    if (!exp_a && !mant_a)
        return (bf16_t) {.bits = result_sign << 15};

    if (exp_a)
        mant_a |= 0x80;
    if (exp_b)
        mant_b |= 0x80;

    uint32_t dividend = (uint32_t) mant_a << 15;
    uint32_t divisor = mant_b;
    uint32_t quotient = 0;

    for (int i = 0; i < 16; i++) {
        quotient <<= 1;
        if (dividend >= (divisor << (15 - i))) {
            dividend -= (divisor << (15 - i));
            quotient |= 1;
        }
    }

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

    if (!exp_a)
        result_exp--;
    if (!exp_b)
        result_exp++;

    if (quotient & 0x8000)
        quotient >>= 8;
    else {
        while (!(quotient & 0x8000) && result_exp > 1) {
            quotient <<= 1;
            result_exp--;
        }
        quotient >>= 8;
    }
    quotient &= 0x7F;

    if (result_exp >= 0xFF)
        return (bf16_t) {.bits = (result_sign << 15) | 0x7F80};
    if (result_exp <= 0)
        return (bf16_t) {.bits = result_sign << 15};
    return (bf16_t) {
        .bits = (result_sign << 15) | ((result_exp & 0xFF) << 7) |
                (quotient & 0x7F),
    };
}

static inline bf16_t bf16_sqrt(bf16_t a)
{
    uint16_t sign = (a.bits >> 15) & 1;
    int16_t exp = ((a.bits >> 7) & 0xFF);
    uint16_t mant = a.bits & 0x7F;

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

    /* sqrt(0) = 0 (handle both +0 and -0) */
    if (!exp && !mant)
        return BF16_ZERO();

    /* sqrt of negative number is NaN */
    if (sign)
        return BF16_NAN();

    /* Flush denormals to zero */
    if (!exp)
        return BF16_ZERO();

    /* Direct bit manipulation square root algorithm */
    /* For sqrt: new_exp = (old_exp - bias) / 2 + bias */
    int32_t e = exp - BF16_EXP_BIAS;
    int32_t new_exp;
    
    /* Get full mantissa with implicit 1 */
    uint32_t m = 0x80 | mant;  /* Range [128, 256) representing [1.0, 2.0) */
    
    /* Adjust for odd exponents: sqrt(2^odd * m) = 2^((odd-1)/2) * sqrt(2*m) */
    if (e & 1) {
        m <<= 1;  /* Double mantissa for odd exponent */
        new_exp = ((e - 1) >> 1) + BF16_EXP_BIAS;
    } else {
        new_exp = (e >> 1) + BF16_EXP_BIAS;
    }
    
    /* 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++;
    } else if (result < 128) {
        while (result < 128 && new_exp > 1) {
            result <<= 1;
            new_exp--;
        }
    }
    
    /* 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();
    
    return (bf16_t) {.bits = ((new_exp & 0xFF) << 7) | new_mant};
}
```
:::

## Assembly
:::spoiler `hw1/hw1-bf16.s`
```asm=
.data
zero_data: 
  .half 0x0000, 0x8000
nan_data:
  .half 0x7fff,0xffff,0x7f81, 0xff81
inf_data:
  .half 0x7f80,0xff80
f32_data:
  .word 0x3f8f86c2 # 1.1213
  .word 0x41336873 # 11.213

bf16_data:
  .half 0x3f90,0x4133 # 1.1213, 11.213
  .half 0x4133,0xbf90 # 11.213, -1.1213
  .half 0xbf90,0x4133 # -1.1213, 11.213
  .half 0xc133,0xbf90 # -11.213, -1.1213

gold_data_add:
  .word 0x4145,0x4121,0x4121,0xc145
gold_data_sub:
  .word 0xc121,0x4145,0xc145,0xc121
gold_data_mul:
  .word 0x4149,0xc149,0xc149,0x4149
gold_data_div:
  .word 0x3dcd,0xc11f,0xbdcd,0x411f
gold_data_sqrt:
  .word 0x3f88,0x4056,0x7fc0,0x7fc0
  
str_plus: .string " + "
str_min: .string " - "
str_mul: .string " * "
str_div: .string " / "
str_sqrt: .string " ^0.5 "
str_equal: .string " is "
str_not: .string " not "
str_nl: .string "\n"
str_passed: .string ", passed!\n"
str_failed: .string ", failed!\n"

  
.text
main:
  jal test_add
  jal test_sub
  jal test_mul
  jal test_div
  jal test_sqrt
  li a7,10
  ecall

#################
#   test_add    #
#################
test_add:
  addi sp,sp,-4
  sw ra,0(sp)
  la s0,bf16_data
  la s1,gold_data_add
  li s2,4      # data counter
test_add_loop:
  addi s2,s2,-1
  bltz s2,test_add_exit
  slli t0,s2,2
  add t1,s0,t0
  add t2,s1,t0
  lhu a1,0(t1) # op a
  lhu a2,2(t1) # op b
  lhu s3,0(t2) # answer

  addi sp,sp,-20
  sw ra,0(sp)
  sw s0,4(sp)
  sw s1,8(sp)
  sw s2,12(sp)
  sw s3,16(sp)
  jal bf16_add
  lw ra,0(sp)
  lw s0,4(sp)
  lw s1,8(sp)
  lw s2,12(sp)
  lw s3,16(sp)
  addi sp,sp,20
  
  mv s4,a0 # result: s4
  
  mv a0, a1
  li a7, 34
  ecall
  
  la a0, str_plus
  li a7,4
  ecall
  
  mv a0, a2
  li a7,34
  ecall
  
  la a0, str_equal
  li a7,4
  ecall
  
  mv a0, s3 # ans
  li a7,34
  ecall
  
  beq s3,s4,test_add_passed
  
  la a0, str_not
  li a7,4
  ecall
  
  mv a0,s4 # wrong result
  li a7,34
  ecall
  
  la a0,str_failed
  li a7,4
  ecall
  
  j test_add_loop
test_add_passed:
  la a0,str_passed
  li a7,4
  ecall
  j test_add_loop
test_add_exit:
  lw ra,0(sp)
  addi sp,sp,4
  ret

#################
#   test_sub    #
#################
test_sub:
  addi sp,sp,-4
  sw ra,0(sp)
  la s0,bf16_data
  la s1,gold_data_sub
  li s2,4      # data counter
test_sub_loop:
  addi s2,s2,-1
  bltz s2,test_sub_exit
  slli t0,s2,2
  add t1,s0,t0
  add t2,s1,t0
  lhu a1,0(t1) # op a
  lhu a2,2(t1) # op b
  lhu s3,0(t2) # answer

  addi sp,sp,-20
  sw ra,0(sp)
  sw s0,4(sp)
  sw s1,8(sp)
  sw s2,12(sp)
  sw s3,16(sp)
  jal bf16_sub
  lw ra,0(sp)
  lw s0,4(sp)
  lw s1,8(sp)
  lw s2,12(sp)
  lw s3,16(sp)
  addi sp,sp,20
  
  mv s4,a0 # result: s4
  
  mv a0, a1
  li a7, 34
  ecall
  
  la a0, str_min
  li a7,4
  ecall
  
  mv a0, a2
  li a7,34
  ecall
  
  la a0, str_equal
  li a7,4
  ecall
  
  mv a0, s3 # ans
  li a7,34
  ecall
  
  beq s3,s4,test_sub_passed
  
  la a0, str_not
  li a7,4
  ecall
  
  mv a0,s4 # wrong result
  li a7,34
  ecall
  
  la a0,str_failed
  li a7,4
  ecall
  
  j test_sub_loop
test_sub_passed:
  la a0,str_passed
  li a7,4
  ecall
  j test_sub_loop
test_sub_exit:
  lw ra,0(sp)
  addi sp,sp,4
  ret

#################
#   test_mul    #
#################
test_mul:
  addi sp,sp,-4
  sw ra,0(sp)
  la s0,bf16_data
  la s1,gold_data_mul
  li s2,4      # data counter
test_mul_loop:
  addi s2,s2,-1
  bltz s2,test_mul_exit
  slli t0,s2,2
  add t1,s0,t0
  add t2,s1,t0
  lhu a1,0(t1) # op a
  lhu a2,2(t1) # op b
  lhu s3,0(t2) # answer

  addi sp,sp,-20
  sw ra,0(sp)
  sw s0,4(sp)
  sw s1,8(sp)
  sw s2,12(sp)
  sw s3,16(sp)
  jal bf16_mul
  lw ra,0(sp)
  lw s0,4(sp)
  lw s1,8(sp)
  lw s2,12(sp)
  lw s3,16(sp)
  addi sp,sp,20
  
  mv s4,a0 # result: s4
  
  mv a0, a1
  li a7, 34
  ecall
  
  la a0, str_mul
  li a7,4
  ecall
  
  mv a0, a2
  li a7,34
  ecall
  
  la a0, str_equal
  li a7,4
  ecall
  
  mv a0, s3 # ans
  li a7,34
  ecall
  
  beq s3,s4,test_mul_passed
  
  la a0, str_not
  li a7,4
  ecall
  
  mv a0,s4 # wrong result
  li a7,34
  ecall
  
  la a0,str_failed
  li a7,4
  ecall
  
  j test_mul_loop
test_mul_passed:
  la a0,str_passed
  li a7,4
  ecall
  j test_mul_loop
test_mul_exit:
  lw ra,0(sp)
  addi sp,sp,4
  ret

#################
#   test_div    #
#################
test_div:
  addi sp,sp,-4
  sw ra,0(sp)
  la s0,bf16_data
  la s1,gold_data_div
  li s2,4      # data counter
test_div_loop:
  addi s2,s2,-1
  bltz s2,test_div_exit
  slli t0,s2,2
  add t1,s0,t0
  add t2,s1,t0
  lhu a1,0(t1) # op a
  lhu a2,2(t1) # op b
  lhu s3,0(t2) # answer

  addi sp,sp,-20
  sw ra,0(sp)
  sw s0,4(sp)
  sw s1,8(sp)
  sw s2,12(sp)
  sw s3,16(sp)
  jal bf16_div
  lw ra,0(sp)
  lw s0,4(sp)
  lw s1,8(sp)
  lw s2,12(sp)
  lw s3,16(sp)
  addi sp,sp,20
  
  mv s4,a0 # result: s4
  
  mv a0, a1
  li a7, 34
  ecall
  
  la a0, str_div
  li a7,4
  ecall
  
  mv a0, a2
  li a7,34
  ecall
  
  la a0, str_equal
  li a7,4
  ecall
  
  mv a0, s3 # ans
  li a7,34
  ecall
  
  beq s3,s4,test_div_passed
  
  la a0, str_not
  li a7,4
  ecall
  
  mv a0,s4 # wrong result
  li a7,34
  ecall
  
  la a0,str_failed
  li a7,4
  ecall
  
  j test_div_loop
test_div_passed:
  la a0,str_passed
  li a7,4
  ecall
  j test_div_loop
test_div_exit:
  lw ra,0(sp)
  addi sp,sp,4
  ret

#################
#   test_sqrt    #
#################
test_sqrt:
  addi sp,sp,-4
  sw ra,0(sp)
  la s0,bf16_data
  la s1,gold_data_sqrt
  li s2,4      # data counter
test_sqrt_loop:
  addi s2,s2,-1
  bltz s2,test_sqrt_exit
  slli t0,s2,2
  add t1,s0,t0
  add t2,s1,t0
  lhu a1,0(t1) # op a
  lhu s3,0(t2) # answer

  addi sp,sp,-20
  sw ra,0(sp)
  sw s0,4(sp)
  sw s1,8(sp)
  sw s2,12(sp)
  sw s3,16(sp)
  jal bf16_sqrt
  lw ra,0(sp)
  lw s0,4(sp)
  lw s1,8(sp)
  lw s2,12(sp)
  lw s3,16(sp)
  addi sp,sp,20
  
  mv s4,a0 # result: s4
  
  mv a0, a1
  li a7, 34
  ecall
  
  la a0, str_sqrt
  li a7,4
  ecall

  la a0, str_equal
  li a7,4
  ecall
  
  mv a0, s3 # ans
  li a7,34
  ecall
  
  beq s3,s4,test_sqrt_passed
  
  la a0, str_not
  li a7,4
  ecall
  
  mv a0,s4 # wrong result
  li a7,34
  ecall
  
  la a0,str_failed
  li a7,4
  ecall
  
  j test_sqrt_loop
test_sqrt_passed:
  la a0,str_passed
  li a7,4
  ecall
  j test_sqrt_loop
test_sqrt_exit:
  lw ra,0(sp)
  addi sp,sp,4
  ret

#################
#  bf16_isnan   #
#################
bf16_isnan:
  li s1,0x7F80 # BF16_EXP_MASK 
  and t0,a0,s1 # a & exp_mask
  bne t0,s1,bf16_isnan_false
  # check mantissa part is non-zero
  andi t0,a0,0x7F
  beq t0,x0,bf16_isnan_false 
  li a1,1
  ret
bf16_isnan_false:
  li a1,0
  ret

#################
#  bf16_isinf   #
#################
bf16_isinf:
  li s1,0x7F80 # BF16_EXP_MASK 
  and t0,a0,s1 # a & exp_mask
  bne t0,s1,bf16_isinf_false
  # check mantissa part is zero
  andi t0,a0,0x7F
  bne t0,x0,bf16_isinf_false 
  li a1,1
  ret
bf16_isinf_false:
  li a1,0
  ret
  
#################
#  bf16_iszero  #
#################
bf16_iszero:
  li s2,0x7FFF # mask exp and mant
  and t0,a0,s2
  bne t0,x0,bf16_iszero_false
  li a1,1
  ret
bf16_iszero_false:
  li a1,0
  ret

#################
#  f32_to_bf16  #
#################
f32_to_bf16:
  li s2,0x7FFF # mask exp and mant
  mv t0,a1 # t0: f32bits
  srli t1,t0,23
  andi t1,t1,0xFF
  addi t1,t1,-0xFF
  bne t1,x0,f32_to_bf16_else
  srli t1,t0,16
  li t0,0xFFFF
  and a0,t1,t0
  ret
f32_to_bf16_else:
  srli t1,t0,16
  andi t1,t1,1
  add t1,t1,s2
  add t0,t0,t1
  srli a0,t0,16
  ret
  
#################
#  bf16_to_f32  #
#################
bf16_to_f32:
  slli a0,a1,16
  ret
#################
#    bf16_add   #
#################
bf16_add:         # a1: a, a2:b
  srli s0,a1,15
  andi s0,s0,1    # s0: sign_a
  srli s1,a2,15   
  andi s1,s1,1    # s1: sign_b
  srli s2,a1,7
  andi s2,s2,0xFF # s2: exp_a
  srli s3,a2,7
  andi s3,s3,0xFF # s3: exp_b
  andi s4,a1,0x7F # s4: mant_a
  andi s5,a2,0x7F # s5: mant_b

  li t0,0xFF  # t0: 0xFF
  bne s2,t0,exp_a_max_n
  beq s4,x0,ret_a
  bne s3,t0,ret_a
  bnez s5,ret_b
  bne s0,s1,ret_nan
exp_a_max_n:
  beq s3,t0,ret_b
  bne s2,x0,a_nonzero
  beq s4,x0,ret_b
a_nonzero:
  bne s3,x0,b_nonzero
  beq s5,x0,ret_a
b_nonzero:
  beq s2,x0,a_denorm
  ori s4,s4,0x80
a_denorm:
  beq s3,x0,b_denorm
  ori s5,s5,0x80
b_denorm:
  sub t1,s2,s3 # exp_diff: t1
  ble t1,x0,exp_diff_neg
  mv t3,s2 # result_exp: t3
  beq t1,x0,res_exp_exit # exp_diff is 0
  addi t4,t1,-8
  bgt t4,x0,ret_a
  srl s5,s5,t1
  j res_exp_exit
exp_diff_neg:
  mv t3,s3 # result_exp: t3
  addi t4,t1,8
  blt t4,x0,ret_b
  sub t4,x0,t1
  srl s4,s4,t4
res_exp_exit:
  bne s0,s1,sign_is_diff
  mv t2,s0 # result_sign = t2
  add t4,s4,s5 # result_mant = t4
  andi t5,t3,0x100
  beq t5,x0,ret_encoded # return if no carry
  srli t4,t4,1
  addi t3,t3,1
  bge t3,t0,ret_inf
  j ret_encoded
sign_is_diff:
  blt s4,s5,b_lt_a
  mv t2,s0
  sub t4,s4,s5
  j b_lt_a_exit
b_lt_a:
  mv t2,s1
  sub t4,s5,s4
b_lt_a_exit:
  beq t4,x0,ret_zero
denorm_loop:
  andi t5,t4,0x80
  bne t5,x0,ret_encoded
  slli t4,t4,1
  addi t3,t3,-1
  blez t3,ret_zero
  j denorm_loop

#################
#    bf16_sub   #
#################
bf16_sub:         # a1: a, a2:b
  li t0, 0x8000 # BF16_SIGN_MASK 
  xor a2,a2,t0
  j bf16_add

#################
#    bf16_mul   #
#################
bf16_mul:         # a1: a, a2:b
  srli s0,a1,15
  andi s0,s0,1    # s0: sign_a
  srli s1,a2,15   
  andi s1,s1,1    # s1: sign_b
  srli s2,a1,7
  andi s2,s2,0xFF # s2: exp_a
  srli s3,a2,7
  andi s3,s3,0xFF # s3: exp_b
  andi s4,a1,0x7F # s4: mant_a
  andi s5,a2,0x7F # s5: mant_b

  xor t2,s0,s1 # res_sign: t2

  li t0,0xFF  # t0: 0xFF
  bne s2,t0,mul_exp_a_max_n
  bnez s4,ret_a
  bnez s3,ret_inf
  beqz s5,ret_nan
  j ret_inf
mul_exp_a_max_n:
  bne s3,t0,mul_exp_b_max_n
  bnez s5,ret_b
  bnez s2,ret_inf
  beqz s4,ret_nan
  j ret_inf
mul_exp_b_max_n:
  li t1,0    # exp_adj: t1
mant_a_norm_loop:
  bnez s2,exp_a_nonzero
  ori t5,s4,0x80
  bnez t5,mant_a_norm
  slli s4,s4,1
  addi t1,t1,-1
  j mant_a_norm_loop
mant_a_norm:
  li s2,1
  j mant_b_norm_loop
exp_a_nonzero:
  ori s4,s4,0x80
mant_b_norm_loop:
  bnez s3,exp_b_nonzero
  ori t5,s5,0x80
  bnez t5,mant_b_norm
  slli s5,s5,1
  addi t1,t1,-1
  j mant_b_norm_exit
mant_b_norm:
  li s3,1
  j mant_b_norm_exit
exp_b_nonzero:
  ori s5,s5,0x80
mant_b_norm_exit:

  # Multiply
  li t4,0
mult_loop:
  beqz s5,mult_exit
  add t4,t4,s4
  addi s5,s5,-1
  j mult_loop
mult_exit:
  add t3,s2,s3
  addi t3,t3,-127
  add t3,t3,t1 # result_exp
  
  li t5,0x8000
  and t5,t4,t5
  beqz t5,result_mant_adj_n
  srli t4,t4,8
  andi t4,t4,0x7F
  addi t3,t3,1
  j check_result_exp
result_mant_adj_n:
  srli t4,t4,7
  andi t4,t4,0x7F
  
check_result_exp:
  blt t3,t0,result_inf_n
  j ret_inf
result_inf_n:
  bgtz t3,ret_encoded
  addi t5,t3,6
  bltz t5,ret_zero
  li t5,1
  sub t5,t5,t3
  srl t4,t4,t5
  li t3,0
  j ret_encoded

#################
#    bf16_div   #
#################
bf16_div:         # a1: a, a2:b
  srli s0,a1,15
  andi s0,s0,1    # s0: sign_a
  srli s1,a2,15   
  andi s1,s1,1    # s1: sign_b
  srli s2,a1,7
  andi s2,s2,0xFF # s2: exp_a
  srli s3,a2,7
  andi s3,s3,0xFF # s3: exp_b
  andi s4,a1,0x7F # s4: mant_a
  andi s5,a2,0x7F # s5: mant_b

  xor t2,s0,s1 # result_sign: t2

  li t0,0xFF  # t0: 0xFF
  bne s3,t0,div_exp_b_max_n
  bnez s5,ret_b
  bne s2,t0,ret_zero
  beqz s4,ret_nan
  j ret_zero
  
div_exp_b_max_n:
  bnez s3,div_b_zero_n
  bnez s5,div_b_zero_n
  bnez s2,ret_inf
  beqz s4,ret_nan # 0/0 is nan
  j ret_inf # inf otherwise

div_b_zero_n:
  bne s2,t0,div_exp_a_max_n
  bnez s4,ret_a
  j ret_inf

div_exp_a_max_n: # check if a is zero
  bnez s2,div_a_zero_n
  beqz s4,ret_zero
div_a_zero_n:
  beqz s2,div_a_denorm
  ori s4,s4,0x80
div_a_denorm:
  beqz s3,div_b_denorm
  ori s5,s5,0x80
div_b_denorm:
  slli s4,s4,15 # s4: dividend
                # s5: divisor
  li s6,0       # s6: quotient
  
  li t4,15      # t4: i
div_loop:
  bltz t4,div_loop_exit
  slli s6,s6,1
  sll t5,s5,t4
  addi t4,t4,-1
  blt s4,t5,div_loop
  sub s4,s4,t5
  ori s6,s6,1
  j div_loop
div_loop_exit:
  sub t3,s2,s3
  addi t3,t3,127
  bnez s2,div_exp_a_zero_n
  addi s3,s3,-1
div_exp_a_zero_n:
  bnez s3,div_exp_b_zero_n
  addi s3,s3,1
div_exp_b_zero_n:
  li t4,0x8000
  and t5,s6,t4
  beqz t5,quot_loop
  srli s6,s6,8
  j quot_exit
quot_loop: 
  and t5,s6,t4
  bnez t5,quot_loop_exit
  addi t5,t3,-1
  blez t5,quot_loop_exit  
  slli s6,s6,1
  addi t3,t3,-1
  j quot_loop
quot_loop_exit:
  srli s6,s6,8
quot_exit:
  andi s6,s6,0x7F

  mv t4,s6 # quotient as mantissa
  bge t3,t0,ret_inf
  blez t3,ret_zero
  j ret_encoded

#################
#   bf16_sqrt   #
#################
bf16_sqrt:         # a1: a
  srli s0,a1,15
  andi s0,s0,1    # s0: sign
  srli s2,a1,7
  andi s2,s2,0xFF # s2: exp
  andi s4,a1,0x7F # s4: mant

  li t0,0xFF  # t0: 0xFF
  bne s2,t0,sqrt_exp_max_n
  bnez s4,ret_a
  bnez s0,ret_nan
  j ret_a
sqrt_exp_max_n:
  bnez s2,sqrt_a_zero_n
  beqz s4,ret_zero
sqrt_a_zero_n:
  bnez s0,ret_nan
  beqz s2,ret_zero

  addi t1,s2,-127 # t1: e, t2: new_exp
  ori t3,s4,0x80  # t3: m
  
  andi t4,t1,1
  beqz t4,sqrt_even_exp
  slli t3,t3,1
  addi t2,t1,-1
  srli t2,t2,1
  addi t2,t2,127
  j sqrt_adjexp_exit
sqrt_even_exp:
  srli t2,t1,1
  addi t2,t2,127
sqrt_adjexp_exit:
  li a2,90  # low
  li a3,256 # high
  li a4,128 # result
bs_loop:
  bgt a2,a3, bs_exit
  add t4,a2,a3
  srli t4,t4,1 # t4:mid

  # Multiply: t5: prod
  li t5,0
  mv t6,t4
sqrt_mult_loop:
  beqz t6,sqrt_mult_exit
  add t5,t5,t4
  addi t6,t6,-1
  j sqrt_mult_loop
sqrt_mult_exit:
  srli t5,t5,7 # sq: t5
  bgt t5,t3,bs_else
  mv a4,t4
  addi a2,t4,1
  j bs_loop
bs_else:
  addi a3,t4,-1
  j bs_loop
bs_exit:
  li t4,256
  blt a4,t4,sqrt_result_norm_else
  srli a4,a4,1
  addi t3,t3,1
  j sqrt_result_norm_exit
sqrt_result_norm_else:
  li t4,128
sqrt_result_norm_loop:
  bge a4,t4,sqrt_result_norm_exit
  li t5,1
  ble t2,t5,sqrt_result_norm_exit
  slli a4,a4,1 
  addi t2,t2,-1
  j sqrt_result_norm_loop
sqrt_result_norm_exit:
  mv t5,t2 # new_exp
  li t2,0 # sign
  andi t4,a4,0x7F # new_mant
  bge t5,t0,ret_inf
  blez t5,ret_zero

  # ret_encoded
  andi t2,t5,0xFF
  slli t2,t2,7
  or  a0,t4,t2
  ret
  

#################
#    Helper     #
#################
ret_a:
  mv a0,a1
  ret
ret_b:
  mv a0,a2
  ret
ret_nan:
  li a0,0x7FC0
  ret
ret_inf:
  slli t2,t2,15
  li t1,0x7F80
  or a0,t1,t2
  ret
ret_zero:
  slli t1,t1,15
  ori a0,t1,0
  ret
ret_encoded: # t2: sign, t3: exp, t4: mant
  slli t2,t2,15
  and t3,t3,t0
  slli t3,t3,7
  andi t4,t4,0x7F
  or a0,t2,t2
  or a0,a0,t3
  or a0,a0,t4
  ret
```
:::
# 🚩 Applications: Fast Inverse Square Root with BFloat16
## `Q_rsqrt()` Algorithm
### ✳️ Introduction

The `q_rsqrt()` algorithm computes an **approximate reciprocal square root**:

$y \approx \frac{1}{\sqrt{x}}$ for a given input $x$, often in **fixed-point or reduced-precision formats** (e.g., BF16 or Q-format).   This method is widely used in graphics, neural networks, and signal processing due to its **high performance** and **deterministic iteration count**.

---

### 🧩 Mathematical Basis

Given $x > 0$, the reciprocal square root can be expressed as:

$y = \frac{1}{\sqrt{x}} = x^{-\frac{1}{2}}$

For efficient computation, we can **linearize around an initial approximation**:

$y_0 \approx \text{initial guess for } \frac{1}{\sqrt{x}}$

Then, using **Newton–Raphson iteration**, we refine the estimate:

$y_{n+1} = y_n \cdot \left( \frac{3}{2} - \frac{x}{2} y_n^2 \right)$

- $y_n$= current estimate  
- $x$= input value  
- Each iteration **squares $y_n$**, multiplies by $x$, subtracts from $\frac{3}{2}$, then multiplies by $y_n$to converge faster.

This converges **quadratically**, meaning the error roughly **squares** at each step.

---

### 🔢 Initial Approximation

A crucial step is choosing an efficient **initial guess**:

$y_0 \approx \text{magic number} - \frac{1}{2} \cdot \text{bit-level representation of } x$

- In floating-point, this is often implemented by **bit reinterpretation** and subtraction (the “fast inverse square root trick” popularized by Quake III).  
- In fixed-point BF16 or Q-format, a **lookup table** or **linear approximation** is often used for simplicity.

The initial approximation ensures **1–2 iterations** of Newton–Raphson achieve sufficient precision.

---

### 🧠 Algorithmic Steps (Mathematical Form)

Let $x$ be the input, and $y$ the reciprocal square root output:

1. **Compute initial guess $y_0$** based on a linear approximation of $x$ in the chosen numeric format.
2. **Iteratively refine** using Newton–Raphson:

    $y_{k+1} = y_k \cdot \left( 1.5 - 0.5 \cdot x \cdot y_k^2 \right)$

3. **Terminate** after fixed iterations (typically 1–3 for BF16), producing $y \approx 1/\sqrt{x}$.

---
### 🔢 BF16 / Fixed-Point Interpretation

In **BF16 or Q-format**, multiplications and subtractions are performed with **reduced-precision arithmetic**. Key considerations:

1. **Overflow prevention:** Ensure intermediate products fit into wider registers.  
2. **Scaling:** Fixed-point numbers must be **properly scaled** for fractional bits.  
3. **Iteration count:** Typically 1 iteration suffices for BF16, since the format limits achievable precision (~0.5% error).  

Mathematically, the algorithm **mirrors the floating-point Newton–Raphson formula**, but all operations are constrained to the **fixed-point representation**.

---

### 📈 Summary

> 💬 *In essence:*  
> The `q_rsqrt()` algorithm efficiently approximates $\frac{1}{\sqrt{x}}$ by combining a **good initial guess** with **1–2 iterations of Newton–Raphson**, producing a result suitable for **low-precision or real-time arithmetic units**.  
> Its quadratic convergence and simple arithmetic make it ideal for **BF16 or Q-format hardware pipelines**, where predictable latency and minimal cycles are critical.

## 🛠️ Implementation
### C Code with FP32
```clike=
float Q_rsqrt( float number ){
  long i;
  float x2, y;
  const float threehalfs = 1.5F;

  x2 = number * 0.5F;
  y  = number;
  i  = * ( long * ) & y;                      
  i  = 0x5f3759df - ( i >> 1 );              
  y  = * ( float * ) &i;
  y  = y * ( threehalfs - ( x2 * y * y ) );  

  return y;
}
```
### Modified C Code with BF16
```clike=
bf16_t Q_rsqrt_bf16( bf16_t number ){
  uint16_t i;
  bf16_t x2, y;
  bf16_t threehalfs = f32_to_bf16(1.5);

  x2 = bf16_mul(number,f32_to_bf16(0.5));
  y = number;
  i = * ( uint16_t * ) & (y.bits);
  i = 0x5f37 - ( i >> 1 );
  y = (bf16_t) { .bits = *(&i) };
  y = bf16_mul(y, bf16_sub(threehalfs, bf16_mul(x2, bf16_mul(y,y))));
  return y;
}
```
### Modified Assembly Code with BF16
```asm=
.data
str_0: .string "Inverse square root of "
str_1: .string ", BF16: "
str_2: .string ", ans: "
str_3: .string ", elapsed cycles: "
str_nl: .string "\n"

test_data:
  .word 0x379591a2
  .word 0x3f800000
  .word 0x40c80000
  .word 0x42c80000
  .word 0x48a623a4
gold_data:
  .word 0x436CD2C2
  .word 0x3f800000
  .word 0x3ecccccd
  .word 0x3dcccccd
  .word 0x3ae0b3f9
  
.text
main:
  li s0,4         # counter
  la s3,test_data # test_data
  la s4,gold_data # gold_data
loop:
  bltz s0,exit
  slli t0,s0,2
  
  add t1,s3,t0
  lw t1,0(t1)    # test_data
  add s1,s4,t0
  lw s1,0(s1)    # gold_data
  
  la a0,str_0
  li a7, 4
  ecall
  
  mv a0,t1
  li a7,2
  ecall
  
  # BF16:
  la a0,str_1
  li a7, 4
  ecall
 
  # set start time
  li a7,31 
  ecall
  mv s2,a1
  
  mv a1,t1 # test_data

  addi sp,sp,-20
  sw s0,0(sp) # counter
  sw s1,4(sp) # gold_data
  sw s2,8(sp) # #cycle
  sw s3,12(sp)
  sw s4,16(sp)
  
  jal f32_to_bf16
  mv a1,a0
  jal Q_sqrt_bf16
  mv a1,a0
  jal bf16_to_f32
  
  lw s0,0(sp)
  lw s1,4(sp)
  lw s2,8(sp)
  lw s3,12(sp)
  lw s4,16(sp)
  addi sp,sp,20
  
  li a7,2 # a0 is result
  ecall

  # set end time
  li a7,31
  ecall
  sub s2,a0,s2

  la a0,str_2
  li a7,4
  ecall

  mv a0,s1 # gold data
  li a7,2
  ecall

  # elapsed cycles
  la a0,str_3
  li a7,4
  ecall

  mv a0,s2
  li a7,1
  ecall
  
  la a0,str_nl
  li a7,4
  ecall
  
  addi s0,s0,-1
  j loop

exit:
  li a7,10
  ecall

Q_sqrt_bf16: # number: a1
  addi sp,sp,-4
  sw ra,0(sp)
  
  li a2,0x3f00 # 0.5
  jal bf16_mul
  mv a3,a0 # x2: a3

  li t1,0x5f37
  srli a1,a1,1 
  sub a4,t1,a1 # y: a4
  mv a2,a4
  mv a1,a4
  jal bf16_mul # bf16_mul(y,y): a0

  mv a2,a0
  mv a1,a3
  jal bf16_mul # bf16_mul(x2,...)

  mv a2,a0                                
  li a1,0x3fc0 # a1: threehalfs=1.5           
  jal bf16_sub                               

  mv a2,a0
  mv a1,a4         
  jal bf16_mul # bf16_mul(y,...)
  
  lw ra,0(sp)
  addi sp,sp,4
  ret
```

### Test Results
![image](https://hackmd.io/_uploads/ByzrTFqpxl.png)
![Screenshot 2025-10-13 212012](https://hackmd.io/_uploads/HyZ2Hi5Tex.png)

## Algorithm with Shift-Add Multiplication
### ✳️ Introduction
Shift–Add Multiplication is a **bitwise arithmetic method** for performing integer or fixed-point multiplication using only **addition** and **bit shifting**.  
It is conceptually derived from the **binary expansion of the multiplier** and forms the mathematical foundation of hardware multipliers (e.g., array multipliers, Booth multipliers, and iterative ALUs).

---

### 🧩 Mathematical Basis

Let the two **unsigned integers** be:

$\[
A = \text{multiplicand}, \q
B = \text{multiplier}$

where $A, B \in \mathbb{N}$.

The binary representation of $B$ can be written as:

$B = \sum_{i=0}^{n-1} b_i \cdot 2^i, \quad b_i \in \{0,1\}$

Then, the product $P = A \times B$ can be expanded as:

$P = A \times \left(\sum_{i=0}^{n-1} b_i \cdot 2^i\right)
   = \sum_{i=0}^{n-1} (A \cdot b_i) \cdot 2^i$

Since $b_i$ is either 0 or 1, this simplifies to a **conditional accumulation**:

$P = \sum_{i=0}^{n-1} p_i  ,$ $p_i=
\begin{cases}
A \cdot 2^i, & \text{if } b_i = 1 \\
0, & \text{if } b_i = 0
\end{cases}$

Thus, multiplication reduces to:

1. **Shift** the multiplicand $A$ left by $i$ bits.  
2. **Add** it to the running sum if the corresponding bit $b_i$ is 1.


---

### ⚖️ Properties

| Property | Description |
|-----------|--------------|
| **Complexity** | $O(n)$, where $n$ is the number of bits |
| **Operations** | Only requires addition and bit-shift (no direct multiply) |
| **Determinism** | Fixed iteration count; predictable latency |
| **Hardware Parallelism** | Easily parallelizable for array multipliers |
| **Accuracy** | Exact for integer arithmetic; in fixed-point, rounding may occur |

---

> 💬 **In summary:**  
> The Shift–Add multiplication method leverages the binary structure of numbers to express multiplication as a series of conditional shifts and additions. It trades raw instruction complexity for predictable, uniform operations — a crucial property for low-power, fixed-latency arithmetic units, especially in BF16 or other reduced-precision numeric systems.

## 🚀 Implementation
### Modified Assembly Code with BF16
We modify the original substract-accumulating multiplication:
```asm=
...
# Multiply: t4 = t4 * s5
  li t4,0          
mult_loop:         
  beqz s5,mult_exit
  add t4,t4,s4     
  addi s5,s5,-1    
  j mult_loop      
mult_exit:
...
```
to bitwise shift-add multiplication, since:
- Multiplicand and multiplier are **unsigned**.
- Multiplicand and multiplier are **at most 16 bits**.
- It takes $\Theta(16)$ instead of $O(\textrm{value of multiplier})$, which is constant time in **this architecture**.
```asm=
...
# Multiply: t4 = t4 * s5
  li t4, 0            # result = 0
  li t0, 16           # 16 bits to process
mult_loop:
  andi t5,s5,1        # check LSB of multiplier
  beqz t5,skip_add    # if 0, skip addition
  add t4,t4,s4        # if LSB=1, add multiplicand to result

skip_add:
  srli s5,s5,1        # shift multiplier right by 1
  slli s4,s4,1        # shift multiplicand left by 1
  addi t0,t0,-1
  bnez t0,mult_loop
mult_exit:
...
```
### Test Results
![image](https://hackmd.io/_uploads/rJA93t5Tex.png)
![Screenshot 2025-10-13 221038](https://hackmd.io/_uploads/BJMorjqTlx.png)

# 🧮 Evaluation

The evaluation compares the proposed **fast multiplication–based reciprocal square root (`q_rsqrt`)** with the **traditional multiply–accumulate implementation**, both in **BF16 precision**, using **FP32** results as the reference.

| Test Data | Error (to FP32) | Cycles – Traditional Mul. | Cycles – Fast Mul. |
|------------|----------------|---------------------------|--------------------|
| 340253.125 | 0.577% | 5281 | 1010 |
| 100 | 0.586% | 4975 | 1010 |
| 6.25 | 0.586% | 4975 | 1010 |
| 1 | 0.000% | 4869 | 1000 |
| 0.00001783 | 0.075% | 4651 | 1002 |

---

### 💡 Commentary

The **fast multiplication method** achieves a **4.9×–5.2× speedup** over the traditional multiply–accumulate approach, lowering the average cycle count from ~5k to ~1k across all test cases.

Despite this large performance gain, the **numerical accuracy remains within 0.6%** of the FP32 reference — well within the tolerance expected for BF16 precision arithmetic.

Key observations:
- For well-conditioned inputs (e.g., `x = 1`), the fast method achieves **exact FP32 parity (0.000% error)**.  
- Larger or smaller magnitudes show small, consistent rounding errors due to BF16’s reduced mantissa precision.  
- Performance remains **deterministic** (fixed iteration count), making it suitable for **real-time or low-power applications**.

---

### ⚙️ Summary

| Metric | Observation |
|--------|--------------|
| **Speedup** | ~5× faster |
| **Accuracy Loss** | ≤ 0.6% |
| **Determinism** | Fixed cycle count (~1000) |
| **Use Case** | Embedded / real-time FP pipelines, BF16 ALU verification |

> ✅ *Conclusion:*  
> The bitwise shift–add–based fast multiplier preserves accuracy while dramatically improving throughput, making it a practical choice for hardware or firmware-level BF16 math units.
# 📈Analysis
## ⚙️ 5-Stage Pipelined Processor in Ripes

Ripes provides multiple CPU models that can execute the same RISC-V assembly program:

- **Single-cycle processor**:  
  Each instruction completes all operations (fetch, decode, execute, memory access, write-back) within a single clock cycle.

- **5-stage pipelined processor**:  
  Splits instruction execution into five stages (IF, ID, EX, MEM, WB), allowing multiple instructions to execute simultaneously in different stages.  
  This section visualizes and explains each stage.

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

🔹 Instruction Fetch (IF)

- **PC**: Stores the current instruction address (e.g., `PC = 0x00000000`).  
  After each instruction, it updates to the next address.  
- **Adder (+4)**: Computes `PC + 4 = 0x00000004` for the next instruction.  
- **MUX**: Selects the next PC value.  
  - Normally chooses `PC + 4` (sequential execution).  
  - For jumps/branches, selects the target address instead.  
  - Example: for `jal x1, 316`, the next PC becomes `PC + 316`.  
- **Instruction Memory**: Fetches the instruction at the current PC.  
  Here, `addr = 0x00000000` → instruction `0x13C000EF` (`jal x1, 316`).  
- **Compressed Decoder**:  
  Checks if the instruction is a 16-bit compressed form and expands it to 32 bits if necessary.  
  (`jal` here is a 32-bit normal instruction, so it stays the same.)
- **IF/ID Pipeline Register**:  
  Stores the instruction and PC for use in the next cycle by the ID stage.  
  (Enable = green, Clear = red → instruction flows normally to ID.)


---
![image](https://hackmd.io/_uploads/Hy4fzcqagx.png)
🔹 Instruction Decode / Register Fetch (ID)

- **IF/ID Register**: Supplies the current instruction (`0x13C000EF`) and PC.  
- **Decode Unit**:  
  Extracts opcode, rd, rs1, rs2, funct3, and funct7 fields to identify the instruction type (here: JAL).  
- **Register File**:  
  Reads the source registers. Since `jal` doesn’t use sources, Reg1 and Reg2 = `0x00000000`.  
- **Immediate Generator**:  
  Extracts the immediate field → `0x0000013C` (jump offset = 316).  
- **ID/EX Pipeline Register**:  
  Holds decoded values for the next clock cycle (passed to EX).


---
![image](https://hackmd.io/_uploads/HyP7f9cpll.png)
🔹 Execute (EX)

- **ALU**:  
  Performs arithmetic, logic, and address calculations.  
  - Input: `PC = 0x00000000`, `Imm = 0x0000013C`  
  - Output: `Res = 0x0000013C` → Jump Target Address  
- **ALU Input Selectors (MUXes)**:  
  Upper MUX selects PC, lower MUX selects immediate → performs `PC + Imm`.  
- **Branch Unit**:  
  Determines if a branch is taken (`beq`, `bne`, `blt`, `bge`, etc.).  
  For `jal`, the jump always occurs → `Branch taken = true`.  
  The jump target (0x0000013C) is sent back to the IF stage to update PC.


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

🔹 Memory Access (MEM)

- **Data Memory**:  
  The `jal` instruction doesn’t access memory, so this stage simply passes through without changes.


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

🔹 Write Back (WB)

- **MUX (Write-back Selector)**:  
  Chooses which data source will be written to the register file.  
  - In this case: `PC + 4 = 0x00000004`.  
- **Register File**:  
  Writes `0x00000004` to the destination register `x1 (ra)`.  
- **Result**:  
  After execution, `x1 = 0x00000004`, which stores the return address (next instruction address).


---

💡 **Summary**

This 5-stage pipeline allows multiple instructions to be processed simultaneously in different stages, improving CPU throughput and overall efficiency.

# Reference
[Inverse Square Root](https://ece.uwaterloo.ca/~dwharder/aads/Algorithms/Inverse_square_root/)