# Assignment1: RISC-V Assembly and Instruction Pipeline Contributed by <[moonchin819](https://github.com/moonchin819)> ## Implementation :::info **🛈 AI Tools Usage** I utilized AI tools to support my work in this assignment. Specifically, Claude was used for code explanations, and Perplexity for clarifying technical concepts. ::: Here's the [source code](https://github.com/moonchin819/ca2025-quizzes). ## [Problem B](https://hackmd.io/@sysprog/arch2025-quiz1-sol#Problem-B) ### uf8_decode 1. Extract last four bits as mantissa, and the high fours bit as the exponent part by shift logically right. 2. Generate 1's according to the number of exponent * e.g. exponent = 3, 0x7FFF >> 12 = 0x0007, and there's actual three 1's * And then move logically left for four bits to align offset to the mantissa part, to avoid overlapping and the continuity of the decode results \begin{gather*}D(b) = m \cdot 2^e + (2^e - 1) \cdot 16\end{gather*} 3. Mantissa controls the detail in every segments of exponent ### Method 1 - Arithmetic Computation ```asm= uf8_decode: andi t0, a0, 0x0F # mantissa = f1 & 0xF srli t1, a0, 4 # exponent = f1 >> 4 li t2, 15 sub t2, t2, t1 # 15 - exponent li t3, 0x7FFF srl t3, t3, t2 # 0x7fff >> (15 - exponent) slli t3, t3, 4 # offset << 4 sll t2, t0, t1 # mantissa << exponent add a0, t2, t3 # mantissa + offset ret ``` ### Method 2 - Read-only Memory (ROM) Lookup Table 1. Calculate the value of each exponent and place into a table * In this problem, the range of data is not so large, so I can try sacrifice some memory space to have better execution efficiency 2. We won't have to calculate the offset in the execution, using the value of exponent as the index to look up the table. \begin{gather*}\text{Offset}(e) =(2^{e}-1)\cdot16 \end{gather*} | Exponent | Offset | Range |Step Size| |:---------:|:-------:|:----------------------|:-----:| | 0 | 0 | [0, 15] |1| | 1 | 16 | [16, 47] |2| | 2 | 48 | [48, 111] |4| | 3 | 112 | [112, 239] |8| | 4 | 240 | [240, 495] |16| | 5 | 496 | [496, 1007] |32| |...|...|...|$2^{e}$| | 15 | 524272 | [524272, 1048575] |32768| ```asm= offset_table: .word 0, 16, 48, 112, 240, 496, 1008, 2032, 4080, 8176, 16368, 32752, 65520, 131056, 262128, 524272 uf8_decode: andi t0, a0, 0x0F # mantissa = f1 & 0xF srli t1, a0, 4 # exponent = f1 >> 4 la t2, offset_table slli t3, t1, 2 # offset << 4 add t2, t2, t3 lw t2, 0(t2) sll t0, t0, t1 # mantissa << exponent add a0, t0, t2 # mantissa + offset ret ``` ### Pros and Cons Pros : * It has faster access time * It's ideal for frequent reuse Cons : * It requires more memory space * Not suitable for very large data ranges ### clz * n represents "how many leading zeros are still there" in current prediction (set 32 to assume there's at most 32 0's) * c is how many bits to shift right now (set 16 to test first) * Use a loop to check the higher 16 bits first to check whether all of them are zero -> if not, check the second half part. * Use the core logic of binary * If the result of shifting right ```y!=0``` -> indicates that the leading zeros are less than what is expected -> ```n-=c``` in the mean time, update ```x=y``` to keep on dealing with the right half * If ```y==0```, it means the part that have been moved out are all zero, so we don't have to minus ```n``` * ```c >>=1``` implies we narrow down the search range by half each time, and it's the main concept of binary search. ### Method 1 - Directly Translation ```asm= clz: li t0, 32 # n = 32 li t1, 16 # c = 16 clz_loop: srl t2, a0, t1 # y = x >> c beqz t2, clz_skip # if (y == 0) skip sub t0, t0, t1 # n -= c mv a0, t2 # x = y clz_skip: srli t1, t1, 1 # c >>= 1 bnez t1, clz_loop # while (c != 0) sub a0, t0, a0 # return n - x ret ``` ### Method 2 - Loop Expansion * Since the loop is only executed 5 times (c decades from 16 to 1), we could expand the loop to eliminate all the cost of branches * Implement the code from 16 to 1 in order, keep dividing c by 2 until c be equal to 0 then exit the loop and return the result value. ```asm= clz: li t0, 32 # n = 32 # c = 16 srli t2, a0, 16 # y = x >> 16 beqz t2, c_equal_8 # branch if y is equal to zero addi t0, t0, -16 # n -= 16 mv a0, t2 # x = y c_equal_8: srli t2, a0, 8 # y = x >> 8 beqz t2, c_equal_4 addi t0, t0, -8 # n -= 8 mv a0, t2 # x = y c_equal_4: srli t2, a0, 4 # y = x >> 4 beqz t2, c_equal_2 addi t0, t0, -4 # n -= 4 mv a0, t2 # x = y c_equal_2: srli t2, a0, 2 # y = x >> 2 beqz t2, c_equal_1 addi t0, t0, -2 # n -= 2 mv a0, t2 # x = y c_equal_1: srli t2, a0, 1 # y = x >> 1 beqz t2, iter_end addi t0, t0, -1 # n -= 1 mv a0, t2 # x = y iter_end: sub a0, t0, a0 # return n - x ret ``` * Although number of code lines has more than doubled, this approach reduces approximately 5000 cycles. ![image](https://hackmd.io/_uploads/SycWsssTeg.png) ### uf8_encode :::spoiler Assembly Code ```asm= uf8_encode: addi sp, sp, -4 sw ra, 0(sp) # will call clz later mv t5, a0 # t5 = value li t0, 16 blt t5, t0, ret_value # branch if value is less than 16 jal ra, clz # call function clz, a0 = lz li t0, 31 sub t0, t0, a0 # msb = 31 - lz li t1, 0 # exponent = 0 li t2, 0 # overflow = 0 li t3, 5 blt t0, t3, find_exact_exp # branch to find exact exponent if msb is less than 5 addi t1, t0, -4 # exponent = msb - 4 li t3, 15 ble t1, t3, over # branch to the nearest 1 function if exponent is less than or equal to 15 li t1, 15 # exponent = 15 over: li t3, 0 # e (counter) overflow_loop: slli t6, t2, 1 # overflow << 1 addi t2, t6, 16 # (overflow << 1) + 16 addi t3, t3, 1 # e++ blt t3, t1, overflow_loop # branch to the begining of the loop if e is still less than exponent adjust_loop: blez t1, find_exact_exp # branch if exponent is less than or equal to zero bge t5, t2, find_exact_exp # branch if overflow is greater than or equal to value (unsigned) addi t6, t5, -16 # overflow - 16 srli t5, t6, 1 # (overflow - 16) >> 1 addi t1, t1, -1 # exponent-- j adjust_loop find_exact_exp: li t6, 15 find_exact_exp_loop: bge t1, t6, exact_done # branch if exponent is greater than or equal to 15 slli t3, t2, 1 # overflow << 1 addi t3, t3, 16 # next_overflow = (oveflow << 1) + 16 blt t5, t3, exact_done # branch if value is less than next_overflow mv t2, t3 # overflow = next_overflow addi t1, t1, 1 # exponent++ j find_exact_exp_loop exact_done: sub t0, t5, t2 # value - overflow srl t0, t0, t1 # (value - overflow) >> exponent slli t3, t1, 4 # exponent << 4 or a0, t3, t0 # (expoment << 4) | mantissa ret_value: lw ra, 0(sp) addi sp, sp, 4 jr ra ``` ::: ### Full Assembly Code :::spoiler Assembly Code ```asm= .data msg1: .asciz ": produce value " msg2: .asciz " but encodes back to " msg3: .asciz ": value\n" msg4: .asciz " <= previous_value " msg5: .asciz "All tests passed.\n" msg6: .asciz "Test failed.\n" line_break: .asciz "\n" .align 2 .text .global main main: jal ra, test # start testing beq a0, x0, fail # didn't pass the test la a0, msg5 li a7, 4 # test passed ecall li a7, 10 # set exit code li a0, 0 ecall # successful if exit code is 0 fail: la a0, msg6 li a7, 4 ecall li a7, 10 li a0, 1 ecall # unsuccessful if exit code is 1 test: addi sp, sp, -4 sw ra, 0(sp) li s0, -1 # s0 = previous_value li s1, 1 # s1 = passed (true) li s2, 0 # s2 = i, counter initialized li s3, 256 # end of counter begin_test_loop: mv a0, s2 jal ra, uf8_decode mv s4, a0 # return the value of uf8_decode mv a0, s4 jal ra, uf8_encode mv s5, a0 # return the value of uf8_encode test_if1: beq s2, s5, test_if2 # branch if fl==fl2 mv a0, s2 # printf(fl) li a7, 34 ecall la a0, msg1 li a7, 4 ecall mv a0, s4 li a7, 1 ecall la a0, msg2 li a7, 4 ecall mv a0, s5 li a7, 34 ecall la a0, line_break li a7, 4 ecall li s1, 0 # passed = false test_if2: bgt s4, s0, end_test_loop mv a0, s2 # printf(fl) li a7, 34 ecall la a0, msg3 li a7, 4 ecall mv a0, s4 # printf(value) li a7, 1 ecall la a0, msg4 li a7, 4 ecall mv a0, s0 # printf(previous_value) li a7, 34 ecall la a0, line_break li a7, 4 ecall li s1, 0 # passed = false end_test_loop: mv s0, s4 # previous_value = value addi s2, s2, 1 blt s2, s3, begin_test_loop mv a0, s1 # return passed lw ra, 0(sp) addi sp, sp, 4 jr ra clz: li t0, 32 # n = 32 li t1, 16 # c = 16 clz_loop: srl t2, a0, t1 # y = x >> c beqz t2, clz_skip # if (y == 0) skip sub t0, t0, t1 # n -= c mv a0, t2 # x = y clz_skip: srli t1, t1, 1 # c >>= 1 bnez t1, clz_loop # while (c != 0) sub a0, t0, a0 # return n - x ret offset_table: .word 0, 16, 48, 112, 240, 496, 1008, 2032, 4080, 8176, 16368, 32752, 65520, 131056, 262128, 524272 uf8_decode: andi t0, a0, 0x0F # mantissa = f1 & 0xF srli t1, a0, 4 # exponent = f1 >> 4 la t2, offset_table slli t3, t1, 2 # offset << 4 add t2, t2, t3 lw t2, 0(t2) sll t0, t0, t1 # mantissa << exponent add a0, t0, t2 # mantissa + offset ret uf8_encode: addi sp, sp, -4 sw ra, 0(sp) # will call clz later mv t5, a0 # t5 = value li t0, 16 blt t5, t0, ret_value # branch if value is less than 16 jal ra, clz # call function clz, a0 = lz li t0, 31 sub t0, t0, a0 # msb = 31 - lz li t1, 0 # exponent = 0 li t2, 0 # overflow = 0 li t3, 5 blt t0, t3, find_exact_exp # branch to find exact exponent if msb is less than 5 addi t1, t0, -4 # exponent = msb - 4 li t3, 15 ble t1, t3, over # branch to the nearest 1 function if exponent is less than or equal to 15 li t1, 15 # exponent = 15 over: li t3, 0 # e (counter) overflow_loop: slli t6, t2, 1 # overflow << 1 addi t2, t6, 16 # (overflow << 1) + 16 addi t3, t3, 1 # e++ blt t3, t1, overflow_loop # branch to the begining of the loop if e is still less than exponent adjust_loop: blez t1, find_exact_exp # branch if exponent is less than or equal to zero bge t5, t2, find_exact_exp # branch if overflow is greater than or equal to value (unsigned) addi t6, t5, -16 # overflow - 16 srli t5, t6, 1 # (overflow - 16) >> 1 addi t1, t1, -1 # exponent-- j adjust_loop find_exact_exp: li t6, 15 find_exact_exp_loop: bge t1, t6, exact_done # branch if exponent is greater than or equal to 15 slli t3, t2, 1 # overflow << 1 addi t3, t3, 16 # next_overflow = (oveflow << 1) + 16 blt t5, t3, exact_done # branch if value is less than next_overflow mv t2, t3 # overflow = next_overflow addi t1, t1, 1 # exponent++ j find_exact_exp_loop exact_done: sub t0, t5, t2 # value - overflow srl t0, t0, t1 # (value - overflow) >> exponent slli t3, t1, 4 # exponent << 4 or a0, t3, t0 # (expoment << 4) | mantissa ret_value: lw ra, 0(sp) addi sp, sp, 4 jr ra ``` ::: ### Analysis In this assignment we use [Ripes simulator](https://ripes.me/) to test our code. * Method 1 - Arithmetic Computation * Assembly code size : 165 lines Cycle counts : 40107 ![image](https://hackmd.io/_uploads/HJSPsijalx.png =50%x) * Method 2.1 - ROM Lookup Table without Loop Expansion * Assembly code size : 166 lines Cycle counts : 39851 ![image](https://hackmd.io/_uploads/BJ_cjiiTle.png =50%x) * Method 2.2 - ROM Lookup Table with Loop Expansion * Assembly code size : 182 lines Cycle counts : 35291 ![image](https://hackmd.io/_uploads/SJQ0ioipxe.png =50%x) Method 2.2 ## [Problem C](https://hackmd.io/@sysprog/arch2025-quiz1-sol#Problem-C) BFloat16 is a 16-bit floating point format designed for keeping the dynamic range of float32 but reduce the precision. The value of BFloat16 is calculated as : \begin{gather*}v = (-1)^S \times 2^{E-127} \times \left(1 + \frac{M}{128}\right)\end{gather*} Determine it's positive or negative by sign, and decide multiple by exponent, then finetune the precision of decimals part by mantissa. ### Initial Part :::spoiler Assembly Code ```asm= .globl bf16_isnan bf16_isnan: li t0, 0x7F80 and t1, a0, t0 bne t1, t0, isnan_false li t2, 0x007F and t3, a0, t2 snez a0, t3 ret isnan_false: li a0, 0 ret bf16_isinf: li t0, 0x7F80 and t1, a0, t0 bne t1, t0, isinf_false li t2, 0x007F and t3, a0, t2 seqz a0, t3 ret isinf_false: li a0, 0 ret bf16_iszero: li t0, 0x7FFF and t1, a0, t0 seqz a0, t1 ret .globl f32_to_bf16 f32_to_bf16: addi sp, sp, -4 sw s0, 0(sp) mv s0, a0 srli t0, s0, 23 andi t0, t0, 0xFF li t1, 0xFF bne t0, t1, unspecial srli a0, s0, 16 li t0, 0xFFFF and a0, a0, t0 j f32_to_bf16_done unspecial: srli t0, s0, 16 andi t0, t0, 1 li t1, 0x7FFF add t0, t0, t1 add s0, s0, t0 srli a0, s0, 16 f32_to_bf16_done: lw s0, 0(sp) addi sp, sp, 4 ret .globl bf16_to_f32 bf16_to_f32: slli a0, a0, 16 ret .globl BF16_NAN BF16_NAN: li a0, 0x7FC0 ret .globl BF16_ZERO BF16_ZERO: li a0, 0x0000 ret ``` ::: ### Add ![image](https://hackmd.io/_uploads/SyLS1pPCle.png) Result with three test cases :::spoiler Assembly Code ```asm= .globl bf16_add bf16_add: srli t0, a0, 15 # a.bits >> 15 andi t0, t0, 1 # & 1 srli t1, a1, 15 # b.bits >> 15 andi t1, t0, 1 # & 1 srli t2, a0, 7 # a.bits >> 7 andi t2, t2, 0xFF # & 0xFF srli t3, a1, 7 # b.bits >> 7 andi t3, t3, 0xFF # & 0xFF li t6, 0xFF beq t2, t6, exp_a_checkall j check_exp_b exp_a_checkall: beqz t4, exp_a_not_nan mv a0, a0 ret exp_a_not_nan: beq t3, t6, check1 mv a0, a0 ret check1: bnez t5, return_b1 beq t0, t1, return_b1 li a0, 0x7FC0 ret return_b1: mv a0, a1 ret check_exp_b: beq t3, t6, return_b2 j check_0_a return_b2: mv a0, a1 ret check_0_a: bnez t2, check_0_b bnez t4, check_0_b mv a0, a1 ret check_0_b: bnez t3, norm_a bnez t5, norm_a mv a0, a0 ret norm_a: beqz t2, norm_b ori t4, t4, 0x80 norm_b: beqz t3, end_check1 ori t5, t5, 0x80 end_check1: addi sp, sp, -20 sw s0, 16(sp) # for exp_diff sw s1, 12(sp) # for result_sign sw s2, 8(sp) # for result_exp sw s3, 4(sp) # for result_mant sw s4, 0(sp) sub s0, t2, t3 # exp_diff = exp_a - exp_b blez s0, diff_neg # branch if exp_diff <= 0 mv s2, t2 # result_exp = exp_a li t6, 8 bgt s0, t6, return_a srl t5, t5, s0 j exp_done diff_neg: bgez s0, diff_else mv s2, t3 li t6, -8 blt s0, t6, return_b3 neg s4, s0 srl t4, t4, s4 j exp_done diff_else: mv s2, t2 return_a: mv a0, a0 ret return_b3: mv a0, a1 ret exp_done: bne t0, t1, diff_sign # branch if sign_a != sign_b same_sign: mv s1, t0 add s3, t4, t5 andi t6, s3, 0x100 beqz t6, norm_end srli s3, s3, 1 addi s2, s2, 1 li t6, 0xFF bge s1, t6, overflow_inf j norm_end overflow_inf: slli a0, s1, 15 li t6, 0x7F80 or a0, a0, t6 ret diff_sign: bge t4, t5, manta_>_mantb mv s1, t1 sub s3, t5, t4 j mant_result manta_>_mantb: mv s1, t0 sub s3, t4, t5 mant_result: beqz s3, return_zero norm_loop: andi t6, s3, 0x80 bnez t6, norm_end slli s3, s3, 1 addi s2, s2, -1 blez s2, return_zero j norm_loop norm_end: slli t0, s1, 15 andi t1, s2, 0xFF slli t1, t1, 7 or t0, t0, t1 andi t1, s3, 0x7F or a0, t0, t1 ret return_zero: li a0, 0x0000 ret ``` ::: ### Sub ![image](https://hackmd.io/_uploads/H1cuy6PAgx.png) Result with three test cases :::spoiler Assembly Code ```asm= .globl bf16_sub bf16_sub: li t0, 0x8000 xor a1, a1, t0 j bf16_add ``` ::: ### Mul ![image](https://hackmd.io/_uploads/rJzi1aw0gx.png) Result with three test cases :::spoiler Assembly Code ```asm= .globl bf16_mul bf16_mul: addi sp, sp, -16 sw s0, 0(sp) sw s1, 4(sp) sw s2, 8(sp) sw ra, 12(sp) srli t0, a0, 15 # a.bits >> 15 andi t0, t0, 1 # t0 = sign_a srli t1, a1, 15 # b.bits >> 15 andi t1, t0, 1 # t1 = sign_b srli t2, a0, 7 # a.bits >> 7 andi t2, t2, 0xFF # t2 = exp_a srli t3, a1, 7 # b.bits >> 7 andi t3, t3, 0xFF # t3 = exp_b andi t4, a0, 0x7F # t4 = mant_a andi t5, a1, 0x7F # t5 = mant_b xor s0, t0, t1 # s0 = result_sign li t6, 0xFF bne t2, t6, check_b_exp bnez t4, return_a bnez t3, result_1 bnez t5, result_1 result_1: slli a0, s0, 15 li t6, 0x7F80 or a0, a0, t6 j quit check_b_exp: li t6, 0xFF bne t3, t6, check_0 bnez t5, return_b bnez t2, result_2 bnez t4, result_2 j return_nan result_2: slli a0, s0, 15 li t6, 0x7F80 or a0, a0, t6 j quit check_0: bnez t2, a_not_zero bnez t4, a_not_zero j return_0 a_not_zero: bnez t3, norm_mant bnez t5, norm_mant return_0: slli a0, s0, 15 j quit norm_mant: li s1, 0 # s1 : exp_adjust = 0 bnez t2, norm_a_else # if(!exp_a) norm_loop_a: andi t6, t4, 0x80 bnez t6, norm_loop_a_done slli t4, t4, 1 addi s1, s1, -1 j norm_loop_a norm_loop_a_done: li t2, 1 j check_exp_b_norm norm_a_else: ori t4, t4, 0x80 check_exp_b_norm: bnez t3, else_norm_b norm_loop_b: andi t6, t5, 0x80 bnez t6, norm_b_done slli t5, t5, 1 addi s1, s1, -1 j norm_loop_b norm_b_done: li t3, 1 j mul_mant else_norm_b: ori t5, t5, 0x80 mul_mant: mul s2, t4, t5 # s2 = result_mant add t6, t2, t3 addi t6, t6, -127 add t6, t6, s1 mv s1, t6 # s1 = result_exp li t6, 0x8000 and t6, s2, t6 beqz t6, mult_else srli s2, s2, 8 andi s2, s2, 0x7F addi s1, s1, 1 j check_exp_overflow mult_else: srli s2, s2, 7 addi s2, s2, 0x7F check_exp_overflow: li t6, 0xFF blt s1, t6, underflow_check slli a0, s0, 15 li t6, 0x7F80 or a0, a0, t6 j quit underflow_check: bgt s1, zero, final li t6, -6 blt s1, t6, return_0_udflow li t6, 1 sub t6, t6, s1 srl s2, s2, t6 li s1, 0 j final return_0_udflow: slli a0, s0, 15 j quit final: slli a0, s0, 15 andi s1, s1, 0xFF slli s1, s1, 7 andi s2, s2, 0x7F or a0, a0, s1 or a0, a0, s2 j quit return_a: mv a0, a0 ret return_b: mv a0, a1 ret return_nan: li a0, 0x7FC0 j quit quit: lw s0, 0(sp) lw s1, 4(sp) lw s2, 8(sp) lw ra, 12(sp) addi sp, sp, 16 ret ``` ::: ### Div ![image](https://hackmd.io/_uploads/HJGIsTwCeg.png) Result with three test cases :::spoiler Assembly Code ```asm= .globl bf16_div bf16_div: addi sp, sp, -16 sw s0, 0(sp) sw s1, 4(sp) sw s2, 8(sp) sw s3, 12(sp) srli t0, a0, 15 # a.bits >> 15 andi t0, t0, 1 # & 1 # t0 = sign_a srli t1, a1, 15 # b.bits >> 15 andi t1, t1, 1 # & 1 # t1 = sign_b srli t2, a0, 7 # a.bits >> 7 andi t2, t2, 0xFF # & 0xFF # t2 = exp_a srli t3, a1, 7 # b.bits >> 7 andi t3, t3, 0xFF # & 0xFF # t3 = exp_b andi t4, a0, 0x7F # t4 = mant_a andi t5, a1, 0x7F # t5 = mant_b xor s0, t0, t1 # s0 = result_sign = sign_a ^ sign_b li t6, 0xFF # t6 is for temporal usage bne t3, t6, check_zero # branch if(exp_b != 0xFF) beqz t5, check_inf # branch if mant = 0 => return b mv a0, a1 # return b j recover check_inf: li t6, 0xFF bne t2, t6, result_sign_1 bnez t4, result_sign_1 li a0, 0x7FC0 # return Nan j recover result_sign_1: slli a0, s0, 15 j recover check_zero: bnez t3, check_2_inf bnez t5, check_2_inf bnez t2, result_sign_2 bnez t4, result_sign_2 li a0, 0x7FC0 # return Nan j recover result_sign_2: slli a0, s0, 15 li t6, 0x7F80 or a0, a0, t6 j recover check_2_inf: li t6, 0xFF bne t2, t6, check_div_zero beqz t4, result_3 mv a0, a0 # return a j recover result_3: slli a0, s0, 15 li t6, 0x7F80 or a0, a0, t6 j recover check_div_zero: bnez t2, norm bnez t4, norm slli a0, s0, 15 j recover norm: beqz t2, norm_b ori t4, t4, 0x80 norm_b: beqz t3, norm_end ori t5, t5, 0x80 norm_end: slli s1, t4, 15 # s1 : dividend = mant_a << 15 mv s2, t5 # s2 : divisor = mant_b li s3, 0 # s3 : quotient = 0 li t6, 0 # t6 : i=0 div_loop: li a2, 16 # a3 = 16 bge t6, a2, end_div_loop # branch if i >= 16 slli s3, s3, 1 # quotient <<= 1 li a3, 15 sub a3, a3, t6 # a2 = 15 - i sll a4, s2, a3 # a3 = divisor << (15-i) bltu s1, a4, skip_sub # branch if dividend is less than sub s1, s1, a4 # dividend -= (divisor << (15-i)) ori s3, s3, 1 # quotient |= 1 skip_sub: addi t6, t6, 1 # i++ j div_loop end_div_loop: sub a2, t2, t3 # a2 = exp_a - exp_b addi a2, a2, 127 # a2 = result_exp = exp_a - exp_b + BF16_EXP_BIAS bnez t2, res_b # branch if exp_a != 0 addi a2, a2, -1 res_b: bnez t3, q_check # branch if exp_b != 0 addi a2, a2, 1 q_check: li t6, 0x8000 and a4, s3, t6 # quotient & 0x8000 beqz a4, q_else srli s3, s3, 8 j check_overflow q_else: q_loop: li t6, 0x8000 and a4, s3, t6 # quotient & 0x8000 bnez a4, q_loop_done li t6, 1 ble a2, t6, q_loop_done slli s3, s3, 1 addi a2, a2, -1 j q_loop q_loop_done: srli s3, s3, 8 check_overflow: andi s3, s3, 0x7F li t6, 0xFF bge a2, t6, overflow j check_un overflow: slli a0, s0, 15 li t6, 0x7F80 or a0, a0, t6 j recover check_un: bgt a2, x0, final_result slli a0, s0, 15 j recover final_result: slli a0, s0, 15 andi a2, a2, 0xFF slli a2, a2, 7 or a0, a0, a2 andi s3, s3, 0x7F or a0, a0, s3 recover: lw s0, 0(sp) lw s1, 4(sp) lw s2, 8(sp) lw s3, 12(sp) addi sp, sp, 16 ret ``` ::: ### Square Root ![image](https://hackmd.io/_uploads/B1IvaawRgx.png) Result with four test cases :::spoiler Assembly Code ```asm= bf16_sqrt: addi sp, sp, -32 sw ra, 28(sp) sw s0, 24(sp) sw s1, 20(sp) sw s2, 16(sp) sw s3, 12(sp) sw s4, 8(sp) sw s5, 4(sp) sw s6, 0(sp) srli t0, a0, 15 # Shift right 15 andi t0, t0, 1 # Extract sign bit by and operation with 1 srli t1, a0, 7 # Shift right 7 andi t1, t1, 0xFF # Extract exponent by and operation with 0xFF (8 bits) andi t2, a0, 0x7F # And operation with 0x7F to extract mantissa li t3, 0xFF bne t1, t3, check_zero # Branch if exp is not equal to 0xFF bnez t2, return_a # Branch if mantissa is not equal to zero bnez t0, return_nan # Branch if sign bit is not equal to zero j return_a # Return the value of a (input) check_zero: # First special case or t3, t1, t2 # bnez t3, check_negative j return_zero check_negative: # Second special case bnez t0, return_nan bnez t1, compute_sqrt j return_zero compute_sqrt: addi s0, t1, -127 ori s1, t2, 0x80 andi t3, s0, 1 beqz t3, even_exp slli s1, s1, 1 addi t4, s0, -1 srai t4, t4, 1 addi s2, t4, 127 j binary_search even_exp: srai t4, s0, 1 addi s2, t4, 127 binary_search: li s3, 90 li s4, 256 li s5, 128 search_loop: bgt s3, s4, search_done add t3, s3, s4 srli t3, t3, 1 mv a1, t3 mv a2, t3 jal multiply mv t4, a0 srli t4, t4, 7 bgt t4, s1, search_high mv s5, t3 addi s3, t3, 1 j search_loop search_high: addi s4, t3, -1 j search_loop search_done: li t3, 256 blt s5, t3, check_low srli s5, s5, 1 addi s2, s2, 1 j extract_mant check_low: li t3, 128 bge s5, t3, extract_mant norm_loop: li t3, 128 bge s5, t3, extract_mant li t3, 1 ble s2, t3, extract_mant slli s5, s5, 1 addi s2, s2, -1 j norm_loop extract_mant: andi s6, s5, 0x7F li t3, 0xFF bge s2, t3, return_inf blez s2, return_zero andi t3, s2, 0xFF slli t3, t3, 7 or a0, t3, s6 j cleanup return_zero: li a0, 0 j cleanup return_nan: li a0, 0x7FC0 j cleanup return_inf: li a0, 0x7F80 j cleanup return_a: cleanup: lw s6, 0(sp) lw s5, 4(sp) lw s4, 8(sp) lw s3, 12(sp) lw s2, 16(sp) lw s1, 20(sp) lw s0, 24(sp) lw ra, 28(sp) addi sp, sp, 32 ret multiply: li a0, 0 beqz a2, mult_done mult_loop: andi t0, a2, 1 beqz t0, mult_skip add a0, a0, a1 mult_skip: slli a1, a1, 1 srli a2, a2, 1 bnez a2, mult_loop mult_done: ret ``` ::: ## 5-Stage Pipeline ![image](https://hackmd.io/_uploads/rJE_Eu5axl.png) Here's the block diagram of the standard RISC-V pipeline, it divides instruction execution into **5 stages**, allowing multiple instructions to overlap in execution, one instruction in each stage. This mechanism improves throughput by executing several instructions simultaneously. ### Pipeline Principle In this pipeline mechanism, each instruction passes through five stages in order: IF -> ID -> EX -> MEM -> WB Let's go through each stage and see how it works. ### I-type First, I choose the first instruction after calling the test , ```addi sp, sp, -4``` function to discuss. 1. Instruction Fetch (IF) ![image](https://hackmd.io/_uploads/Hyeditqael.png =40%x) * For Program Counter: Currently holds the value **0x00000040**, which points to the address of the current instruction. * The value is then sent to the **Instruction Memory** as the address input. * Instruction memory reads the 32-bit instruction stored at **0x00000040**. And **0xFFC10113** is the fetched instruction corresponding to the instruction ```addi sp, sp, -4```. * Since `addi` is not a branch or jump instruction, the MUX selects the sequential path PC+4. The PC register is updated to 0x00000044 on the next clock cycle. 2. Instruction Decode (ID) ![image](https://hackmd.io/_uploads/ryxbx55peg.png =45%x) * Instruction is decoded to * Opcode : `0x13`or`ADDI` * r1 idx : 0x02 (x2) (sp) * r2 idx is unused in I-type because I-type instruction adds an immediate to source register. * rd, rs1 : x2 * Immediate : 0xfffffffc (-4) * Reg1 = 0x7FFFFFF0 is the value of current stack pointer, and Reg2 is 0x00000000 stands for unused. * The immediate will be sent into ALU with the output of register. * All the data was then sent to ID/EX pipeline register waiting for being used in next stage. 3. Execute (EX) ![image](https://hackmd.io/_uploads/HJRo8ccpgl.png =45%x) * Since it's `ADDI` I-type instruction, we need not access to memory or jump, just receive the value from register file and execute addition. * Input source, using two MUX to select the inputs of ALU as register value and immediate value: * Op1 : 0x7FFFFFF0 (from register x2 (xp)), represents the original stack pointer * Op2 : 0xFFFFFFFC (equals to immediate -4) * ALU executes the addition according to the control signal ALUOp, and the result is sp-4 = 0x7FFFFFEC * And there's a Branch Unit in the figure, but branch taken is set as 0 since this instruction is not a branch instruction, therefore, PC won't change the direction and keep on executing next instruction. * After EX stage is over, ALU result, destination register and control signals are sent into EX/MEM register. 4. Memory Access (MEM) ![image](https://hackmd.io/_uploads/SJGivc5Tll.png =35%x) * Data Memory module determine whether access or not depends on the control signals. * For this `ADDI` instruction case, we won't access to memory since it's a pure arithmetic instruction, so the WrEn is red and the output of Data Memory module is 0 since no memory data was read. * Pipeline register sends the results to the next stage, which meas it's a pass-through stage. 5. Write Back (WB) ![image](https://hackmd.io/_uploads/HyEI_q5Tlg.png =25%x) * In this stage, there's a crucial MUX to decide whether the write back data is from ALU results or the value read by Data Memory. * Since `ADDI` is a arithmetic instruction, the control signal is 0 and MUX chose ALU result as input then write back to register file. * And we can see the WrEn turns into 1 which means it's available to write in, so the value of sp is updated to 0x7FFFFFEC.