# Assignment 1: RISC-V Assembly and Instruction Pipeline > contributed by < [Urdan0117](https://github.com/Urdan0117) > > N26144387 [name=Chiu Kun Chan] > [color=#2fbeed] **🔹 Use of AI tools** > I used ChatGPT to assist with Quiz 1 by providing code explanations, grammar polishing, preliminary research, code summaries, and explanations of standard RISC-V instruction usage. --- # ProbB ## Abstract This assignment implements **UF8 logarithmic quantization** encoder and decoder in **RV32I**. Besides reproducing the baseline functionality, the key improvements include: * Using `clz` to locate the MSB + an **O(1)** formula `offset(e)=((1<<e)-1)<<4` * **Leafified** `clz` and `uf8_decode` (all `t*` registers, zero `sw/lw`); `uf8_encode` saves only `ra/s0` * Built-in `test` verifies **round-trip** and **monotonicity** * Measured code size / cycles in **Ripes**, and provided 5-stage pipeline and control-signal analysis --- ## 1. Use Case UF8 is suitable for: sensor distance / temperature data, graphics LOD distance / fog density, and exponential-backoff timers. Not suitable for: financial precision computations or cryptographic applications requiring uniform value distribution. --- ## 2. UF8 Format and Algorithm * 8-bit UF8: `fl = (e<<4) | m`, where `e=fl[7:4]`, `m=fl[3:0]` * **Decoding** $$ offset(e) = ((1<<e) - 1) << 4 $$ $$ value = (m << e) + offset(e) $$ * **Encoding (concept)** 1. `msb = 31 - clz(value)`; initial estimate `e0 = clamp(msb-4, 0..15)` 2. Compare with `offset(e)` / `offset(e+1)` (if `e<15`) and adjust `e ± 1` if needed 3. `m = (value - offset(e)) >> e`, combine `fl=(e<<4)|m` * **Edge cases:** `value < 16` returns directly; `e=15` has no `offset(e+1)`. --- ## 3. Version Evolution and Key Fixes (V0 → V1 → V2) ### 3.1 V0 (baseline, functional) First student attempt directly rewritten from C. :exclamation: All used stack frames and saved `ra/s*`; functional but **leaf functions still used frequent sw/lw**. * **decode:** used $$ \text{offset}(e)=(0x7FFF \gg (15-e)) \ll 4 $$ and `value = (m<<e) + offset(e)` * **encode:** implemented **overflow loop** accumulating `(overflow<<=1; +=16)` until `e`; if misestimated, `adjust_overflow` / `find_exact_exp` refines `e` before mantissa computation * **test:** stored **fl in `s3`** (safe across two `jal`); checks **round-trip** + **monotonicity** * **Status:** fully functional & passes tests, but with higher **memory traffic** and **stack overhead** <details> <summary>V0 full code</summary> ```asm # 字串常數 .data success_msg: .string "All tests passed.\n" failure_msg: .string "Tests failed.\n" .text .globl main # 主程式 main: addi sp, sp, -16 sw ra, 0(sp) # 呼叫測試函數 jal ra, test # 檢查測試結果 beq a0, zero, print_failure print_success: la a0, success_msg # a0 = C-string 位址 li a7, 4 # 4: print_string (Ripes 教學環境) ecall li a7, 10 # 10: exit ecall print_failure: la a0, failure_msg li a7, 4 ecall li a7, 10 ecall # 程式入口點 _start: jal ra, main # CLZ - Count Leading Zeros # 輸入: a0 = 32位無號整數 # 輸出: a0 = 前導零的數量 (0-32) clz: # 保存暫存器 addi sp, sp, -16 sw ra, 0(sp) sw s0, 4(sp) sw s1, 8(sp) sw s2, 12(sp) # 檢查輸入是否為零 beq a0, zero, clz_zero # 初始化計數器和移位量 li s0, 0 # count = 0 li s1, 32 # n = 32 li s2, 16 # c = 16 clz_loop: # y = x >> c srl t0, a0, s2 # t0 = a0 >> s2 beq t0, zero, clz_no_shift # 如果 y != 0,則 n -= c, x = y sub s1, s1, s2 # n -= c mv a0, t0 # x = y clz_no_shift: # c >>= 1 srli s2, s2, 1 # c = c >> 1 bne s2, zero, clz_loop # return n - x sub a0, s1, a0 j clz_end clz_zero: li a0, 32 # 如果輸入為0,返回32 clz_end: # 恢復暫存器 lw ra, 0(sp) lw s0, 4(sp) lw s1, 8(sp) lw s2, 12(sp) addi sp, sp, 16 ret # UF8_DECODE - 將 UF8 格式轉換為 32位整數 # 輸入: a0 = UF8 值 (8位) # 輸出: a0 = 32位整數值 uf8_decode: addi sp, sp, -16 sw ra, 0(sp) sw s0, 4(sp) sw s1, 8(sp) # 提取尾數 (lower 4 bits) andi s0, a0, 0x0f # mantissa = fl & 0x0f # 提取指數 (upper 4 bits) srli s1, a0, 4 # exponent = fl >> 4 # 計算偏移量: offset = (0x7FFF >> (15 - exponent)) << 4 li t0, 15 sub t0, t0, s1 # t0 = 15 - exponent li t1, 0x7FFF srl t1, t1, t0 # t1 = 0x7FFF >> (15 - exponent) slli t1, t1, 4 # offset = t1 << 4 # 計算結果: (mantissa << exponent) + offset sll t2, s0, s1 # t2 = mantissa << exponent add a0, t2, t1 # result = (mantissa << exponent) + offset lw ra, 0(sp) lw s0, 4(sp) lw s1, 8(sp) addi sp, sp, 16 ret # UF8_ENCODE - 將 32位整數轉換為 UF8 格式 # 輸入: a0 = 32位整數值 # 輸出: a0 = UF8 值 (8位) uf8_encode: addi sp, sp, -32 sw ra, 0(sp) sw s0, 4(sp) sw s1, 8(sp) sw s2, 12(sp) sw s3, 16(sp) sw s4, 20(sp) sw s5, 24(sp) mv s0, a0 # s0 = value # 如果 value < 16,直接返回 li t0, 16 blt s0, t0, encode_direct # 使用 CLZ 計算指數 jal ra, clz # a0 = clz(value) li t0, 31 sub s1, t0, a0 # msb = 31 - lz # 初始化變數 li s2, 0 # exponent = 0 li s3, 0 # overflow = 0 # 檢查 msb >= 5 li t0, 5 blt s1, t0, find_exact_exp # 估算指數: exponent = msb - 4 addi s2, s1, -4 # exponent = msb - 4 li t0, 15 bgt s2, t0, cap_exponent j calc_overflow cap_exponent: li s2, 15 # exponent = 15 calc_overflow: # 計算初始 overflow li s4, 0 # e = 0 overflow_loop: bge s4, s2, adjust_overflow slli s3, s3, 1 # overflow <<= 1 addi s3, s3, 16 # overflow += 16 addi s4, s4, 1 # e++ j overflow_loop adjust_overflow: # 調整 overflow 如果估算錯誤 beq s2, zero, find_exact_exp bge s0, s3, find_exact_exp addi s3, s3, -16 # overflow -= 16 srli s3, s3, 1 # overflow >>= 1 addi s2, s2, -1 # exponent-- j adjust_overflow find_exact_exp: # 找到精確的指數 li t0, 15 bge s2, t0, calc_mantissa slli t1, s3, 1 # next_overflow = overflow << 1 addi t1, t1, 16 # next_overflow += 16 bge s0, t1, update_exp j calc_mantissa update_exp: mv s3, t1 # overflow = next_overflow addi s2, s2, 1 # exponent++ j find_exact_exp calc_mantissa: # 計算尾數: mantissa = (value - overflow) >> exponent sub t0, s0, s3 # t0 = value - overflow srl s5, t0, s2 # mantissa = t0 >> exponent # 組合結果: (exponent << 4) | mantissa slli t0, s2, 4 # t0 = exponent << 4 or a0, t0, s5 # result = (exponent << 4) | mantissa j encode_end encode_direct: # 直接返回 value (< 16) mv a0, s0 encode_end: lw ra, 0(sp) lw s0, 4(sp) lw s1, 8(sp) lw s2, 12(sp) lw s3, 16(sp) lw s4, 20(sp) lw s5, 24(sp) addi sp, sp, 32 ret # TEST - 測試編碼/解碼的往返轉換 # 輸出: a0 = 1 (通過) 或 0 (失敗) test: addi sp, sp, -32 sw ra, 0(sp) sw s0, 4(sp) # i (loop counter) sw s1, 8(sp) # previous_value sw s2, 12(sp) # current value sw s3, 16(sp) # fl (original) sw s4, 20(sp) # fl2 (re-encoded) sw s5, 24(sp) # passed flag li s0, 0 # i = 0 li s1, -1 # previous_value = -1 li s5, 1 # passed = true test_loop: li t0, 256 bge s0, t0, test_end # fl = i mv s3, s0 # value = uf8_decode(fl) mv a0, s3 jal ra, uf8_decode mv s2, a0 # s2 = decoded value # fl2 = uf8_encode(value) mv a0, s2 jal ra, uf8_encode mv s4, a0 # s4 = re-encoded value # 檢查 fl != fl2 bne s3, s4, test_fail # 檢查 value <= previous_value ble s2, s1, test_fail # 更新 previous_value mv s1, s2 # i++ addi s0, s0, 1 j test_loop test_fail: li s5, 0 # passed = false test_end: mv a0, s5 # return passed lw ra, 0(sp) lw s0, 4(sp) lw s1, 8(sp) lw s2, 12(sp) lw s3, 16(sp) lw s4, 20(sp) lw s5, 24(sp) addi sp, sp, 32 ret ``` </details> --- ### 3.2 V1 (Lightweight + O(1) offset) * **encode:** replaced loop with **O(1) formula** $$ \text{offset}(e)=((1\ll e)-1)\ll 4 $$ > Estimate `e` from `msb`, then use `offset(e)` and `offset(e+1)` for single-step interval correction (`update_exp_up/down`) before computing mantissa. * **decode:** kept `0x7FFF` formula (mathematically equivalent to V1’s O(1) form, including `e=0→offset=0`) * **test results:** passed correctly; encode’s **offset construction cost reduced from O(e) to O(1)**; further improvement possible by full `t*` leaf refactor to remove sw/lw. <details> <summary>V1 encode code</summary> ```asm # ====== O(1) 版:計算 offset(e) = ((1<<e)-1) << 4 ====== calc_overflow: li t1, 1 sll t1, t1, s2 # t1 = 1 << e addi t1, t1, -1 # t1 = (1<<e) - 1 slli s3, t1, 4 # s3 = offset(e) = ((1<<e)-1)<<4 # ====== 調整 e:確保 offset(e) ≤ value < offset(e+1)(若 e<15) ====== find_exact_exp: # 若 e < 15,先算 next_offset = ((1<<(e+1))-1)<<4 li t0, 15 bge s2, t0, calc_mantissa # e==15:無 next_offset,直接去算尾數 addi t2, s2, 1 # t2 = e+1 li t3, 1 sll t3, t3, t2 # t3 = 1<<(e+1) addi t3, t3, -1 # t3 = (1<<(e+1))-1 slli t3, t3, 4 # t3 = next_offset(e+1) # 若 value >= next_offset(e+1) → e++(往上擴) bge s0, t3, update_exp_up # 若 value < offset(e) → e--(往下縮) blt s0, s3, update_exp_down # 否則 offset(e) ≤ value < next_offset(e+1),e 固定 j calc_mantissa update_exp_up: addi s2, s2, 1 # e = e+1 mv s3, t3 # s3 = offset(e)(剛好等於之前算的 next_offset) j find_exact_exp update_exp_down: addi s2, s2, -1 # e = e-1 # 重新 O(1) 計算 offset(e) li t1, 1 sll t1, t1, s2 addi t1, t1, -1 slli s3, t1, 4 j find_exact_exp ``` </details> --- ### 3.3 V2 (Leafified, s→t conversion; caller-saved fix in test) * **clz / decode:** **leafified**, all `t*`, **zero sw/lw** * **encode:** saves only `ra/s0`; all else `t*`; `calc_overflow` uses **O(1) offset** then `find_exact_exp` for single-step range adjust before mantissa * **test:** * Early V2 kept `fl` in `t1`, but cross-`jal` callee (`decode/encode`) overwrote it → occasional round-trip fail * Fixed by storing `fl` in `s3` → problem resolved * **Result:** after fixing `s3` and `msb<5` branches, passes all tests with **minimal sw/lw** and reduced pipeline stalls <details> <summary>V2 full code</summary> ```asm # 字串常數 .data success_msg: .string "All tests passed.\n" failure_msg: .string "Tests failed.\n" .text .globl main # 主程式 main: # 不需要保存 ra,不需要動 sp # 呼叫測試函數 jal ra, test # 檢查測試結果 beq a0, zero, print_failure print_success: la a0, success_msg # a0 = C-string 位址 li a7, 4 # 4: print_string (Ripes 教學環境) ecall li a7, 10 # 10: exit ecall print_failure: la a0, failure_msg li a7, 4 ecall li a7, 10 ecall # 程式入口點 # _start: # jal ra, main # CLZ - Count Leading Zeros # 輸入: a0 = 32位無號整數 # 輸出: a0 = 前導零的數量 (0-32) clz: # 檢查輸入是否為零 beq a0, zero, clz_zero # 初始化計數器和移位量 li t0, 0 # count = 0 li t1, 32 # n = 32 li t2, 16 # c = 16 clz_loop: # y = x >> c srl t3, a0, t2 # t3 = a0 >> t2 beq t3, zero, clz_no_shift # 如果 y != 0,則 n -= c, x = y sub t1, t1, t2 # n -= c mv a0, t3 # x = y clz_no_shift: # c >>= 1 srli t2, t2, 1 # c = c >> 1 bne t2, zero, clz_loop # return n - x sub a0, t1, a0 ret clz_zero: li a0, 32 # 如果輸入為0,返回32 ret # UF8_DECODE - 將 UF8 格式轉換為 32位整數 # 輸入: a0 = UF8 值 (8位) # 輸出: a0 = 32位整數值 uf8_decode: # 提取尾數 (lower 4 bits) andi t4, a0, 0x0f # mantissa = fl & 0x0f # 提取指數 (upper 4 bits) srli t5, a0, 4 # exponent = fl >> 4 # 計算偏移量: offset = (0x7FFF >> (15 - exponent)) << 4 li t0, 15 sub t0, t0, t5 # t0 = 15 - exponent li t1, 0x7FFF srl t1, t1, t0 # t1 = 0x7FFF >> (15 - exponent) slli t1, t1, 4 # offset = t1 << 4 # 計算結果: (mantissa << exponent) + offset sll t2, t4, t5 # t2 = mantissa << exponent add a0, t2, t1 # result = (mantissa << exponent) + offset ret # UF8_ENCODE - 將 32位整數轉換為 UF8 格式 # 輸入: a0 = 32位整數值 # 輸出: a0 = UF8 值 (8位) uf8_encode: addi sp, sp, -8 sw ra, 0(sp) sw s0, 4(sp) # s0 = value(唯一需要跨 call 保留者) mv s0, a0 # s0 = value # 如果 value < 16,直接返回 li t0, 16 blt s0, t0, encode_direct # 使用 CLZ 計算 msb 與初始 exponent mv a0, s0 jal ra, clz # a0 = clz(value) li t1, 31 sub t2, t1, a0 # msb = 31 - lz # 初始化變數 li t3, 0 # exponent = 0 li t4, 0 # overflow = 0 # 檢查 msb >= 5 li t0, 5 blt t2, t0, find_exact_exp # 估算指數: exponent = msb - 4 addi t3, t2, -4 # exponent = msb - 4 li t0, 15 bgt t3, t0, cap_exponent j calc_overflow cap_exponent: li t3, 15 # exponent = 15 # O(1)版:計算 offset(e) = ((1<<e)-1) << 4 calc_overflow: li t5, 1 sll t5, t5, t3 # t5 = 1 << e addi t5, t5, -1 # t5 = (1<<e) - 1 slli t4, t5, 4 # t4 = offset(e) = ((1<<e)-1)<<4 # 調整 e:確保 offset(e) ≤ value < offset(e+1)(若 e<15) find_exact_exp: # 若 e < 15,先算 next_offset = ((1<<(e+1))-1)<<4 li t0, 15 bge t3, t0, calc_mantissa # e==15:無 next_offset,直接去算尾數 addi t6, t3, 1 # t6 = e+1 li t1, 1 sll t1, t1, t6 # t1 = 1<<(e+1) addi t1, t1, -1 # t1 = (1<<(e+1))-1 slli t1, t1, 4 # t1 = next_offset(e+1) # 若 value >= next_offset(e+1) → e++(往上擴) bge s0, t1, update_exp_up # 若 value < offset(e) → e--(往下縮) blt s0, t4, update_exp_down # 否則 offset(e) ≤ value < next_offset(e+1),e 固定 j calc_mantissa update_exp_up: addi t3, t3, 1 # e = e+1 mv t4, t1 # t4 = offset(e)(剛好等於之前算的 next_offset) j find_exact_exp update_exp_down: addi t3, t3, -1 # e = e-1 # 重新 O(1) 計算 offset(e) li t5, 1 sll t5, t5, t3 addi t5, t5, -1 slli t4, t5, 4 j find_exact_exp calc_mantissa: # 計算尾數: mantissa = (value - overflow) >> exponent sub t0, s0, t4 # t0 = value - overflow srl t2, t0, t3 # mantissa = t0 >> exponent # 組合結果: (exponent << 4) | mantissa slli t1, t3, 4 # t1 = exponent << 4 or a0, t1, t2 # result = (exponent << 4) | mantissa j encode_end encode_direct: # 直接返回 value (< 16) mv a0, s0 encode_end: lw ra, 0(sp) lw s0, 4(sp) addi sp, sp, 8 ret # TEST - 測試編碼/解碼的往返轉換 # 輸出: a0 = 1 (通過) 或 0 (失敗) test: addi sp, sp, -20 sw ra, 0(sp) sw s0, 4(sp) # i (loop counter) sw s1, 8(sp) # previous_value sw s2, 12(sp) # decoded value(需跨 encode 呼叫保留) sw s5, 16(sp) # passed li s0, 0 # i = 0 li s1, -1 # previous_value = -1 li s5, 1 # passed = true test_loop: li t0, 256 bge s0, t0, test_end # fl = i mv t1, s0 # value = uf8_decode(fl) mv a0, t1 jal ra, uf8_decode mv s2, a0 # decoded value 要跨下一個 jal 保留 → 放 s2 # fl2 = uf8_encode(value) mv a0, s2 jal ra, uf8_encode mv t2, a0 # fl2 暫存 # 檢查 fl != fl2 bne t1, t2, test_fail # 檢查 value <= previous_value ble s2, s1, test_fail # 更新 previous_value mv s1, s2 # i++ addi s0, s0, 1 j test_loop test_fail: li s5, 0 # passed = false test_end: mv a0, s5 # return passed lw ra, 0(sp) lw s0, 4(sp) lw s1, 8(sp) lw s2, 12(sp) lw s5, 16(sp) addi sp, sp, 20 ret ``` </details> --- ## 4. Version Comparison Table (Quick Summary) | Aspect | V0 | V1 | V2 | | :------------------ | :-------------------------- | :--------------------------------------------- | :----------------------------------------- | | `clz` | Uses stack & `s*` | Same as V0 | **Leaf**, all `t*`, zero `sw/lw` | | `uf8_decode` | Stack, `0x7FFF>>(15-e)<<4` | Same as V0 | **Leaf**, all `t*`, still `0x7FFF` formula | | `uf8_encode` | Loop builds overflow (O(e)) | **O(1)** `((1<<e)-1)<<4`; `update_exp_up/down` | Same as V1, saves only `ra/s0` | | `test` (fl storage) | `s3` (safe) | `s3` (safe) | Originally `t1`, later → `s3` | | Memory traffic | High | Medium | **Low** | --- ## 5. Testing (Automation + Representative Values) ### 5.1 Automated Validation * **Round-trip:** For `fl∈[0..255]`, check `encode(decode(fl))==fl` * **Monotonicity:** For `v(fl)=decode(fl)`, verify `v(fl+1)>v(fl)` * Prints `All tests passed.` on success; otherwise `Tests failed.` ### 5.2 Representative Manual Examples UF8 rule: `value = (m<<e) + ((1<<e)-1)<<4` | UF8 `fl` | decimal | e | m | decoded `value` | re-encoded | Result | | :------: | ------: | -: | -: | --------------: | ---------: | :----: | | `0x00` | 0 | 0 | 0 | 0 | `0x00` | ✅ | | `0x10` | 16 | 1 | 0 | 16 | `0x10` | ✅ | | `0x7F` | 127 | 7 | 15 | 3952 | `0x7F` | ✅ | | `0xF0` | 240 | 15 | 0 | 524 272 | `0xF0` | ✅ | | `0xFF` | 255 | 15 | 15 | 1 015 792 | `0xFF` | ✅ | --- ## 6. Performance and Code Size (Ripes Measurement) ### 6.1 Runtime Metrics (`fl=0..255` test) | Version | Cycles | Instr. Retired | CPI | IPC | L1-Data Accesses | | :------ | -----: | -------------: | ---: | ----: | ---------------: | | **V0** | 53 666 | 39 387 | 1.36 | 0.734 | 7 055 | | **V1** | 40 214 | 30 651 | 1.31 | 0.762 | 7 055 | | **V2** | 32 466 | 23 879 | 1.36 | 0.736 | 1 036 | > Overhead ≈ `Cycles−Instrs.` > V0 14 279 V1 9 563 V2 8 587 --- ### 6.2 Improvements (Relative) 1. **Cycles** * V1 vs V0: −25.1% (53 666 → 40 214) * V2 vs V0: −39.5% (53 666 → 32 466) * V2 vs V1: −19.3% (40 214 → 32 466) 2. **Instructions** * V1 vs V0: −22.2% (39 387 → 30 651) * V2 vs V0: −39.4% (39 387 → 23 879) * V2 vs V1: −22.1% (30 651 → 23 879) 3. **CPI** * V1 vs V0: 1.36 → 1.31 (↑ 3.7%) * V2 vs V1: 1.31 → 1.36 (slight regression) > Although V2 CPI rose slightly, its 22% fewer instructions still yield fewer total cycles. 4. **L1-Data Accesses** * V2 vs V0/V1: 7 055 → 1 036 (−85.3%) → directly shows the benefit of **leafification** and reduced stack `sw/lw`. --- ### 6.3 Code Size (per function) Computation: $$ size(clz)=decode−clz $$ $$ size(uf8_decode)=encode−decode $$ $$ size(uf8_encode)=test−encode $$ **Start addresses** * V0 clz 0x44 decode 0xA4 encode 0xF0 test 0x1DC * V1 clz 0x40 decode 0xA0 encode 0xEC test 0x1DC * V2 clz 0x38 decode 0x70 encode 0x9C test 0x16C | Version | `clz` (B/I) | `uf8_decode` | `uf8_encode` | **Sum** | | :------ | ----------: | -----------: | -----------: | --------: | | **V0** | 96 / 24 | 76 / 19 | 236 / 59 | 408 / 102 | | **V1** | 96 / 24 | 76 / 19 | 240 / 60 | 412 / 103 | | **V2** | 56 / 14 | 44 / 11 | 208 / 52 | 308 / 77 | **Improvement** * V2 vs V0 (total): 408 → 308 B (−24.5%) * V2 vs V1 (total): 412 → 308 B (−25.2%) * Per function: `clz −41.7%`, `decode −42.1%`, `encode −11.9% (vs V0)` > Observation: V1’s formulaic offset slightly increased encode size (236→240 B); V2 via **leafification + register reallocation** minimized all three functions, with `clz/decode` shrinking > 40%. --- ## 7. References * AI Tool (ChatGPT) * Ripes (environment calls) / RISC-V ISA (RV32I) * UF8 and bit hacks (CLZ) * [Quiz 1 Solution](https://hackmd.io/@sysprog/arch2025-quiz1-sol#Problem-C) * [Lab 1](https://hackmd.io/@sysprog/H1TpVYMdB) --- # ProbC ## 1. Abstract This project implements **BFloat16 arithmetic** on **RV32I** using **pure integer operations**: `f32_to_bf16`, `bf16_to_f32`, `add_bf16`, `sub_bf16`, `mul_bf16` (with round-to-nearest-even, RNE), `div_bf16` (integer long division), and `sqrt_bf16` (binary search). IEEE-754 corner cases are fully handled: **NaN / Inf / ±0**, and **subnormal numbers are flushed to zero**. All programs run correctly in **Ripes**. This submission directly presents a one-step upgrade **from V0 → V1** (V1 = final version): 1. **Simplified `main` test harness**: removed redundant “clear upper 16 bits” operations. 2. **`add_bf16` leafified**: switched to **caller-saved (`a*/t*`)** registers, **no stack frame or `s*` saves**. Normalization after subtraction was changed from a while-loop to a **multi-bit segmented shift (one-step normalize)**. The other routines (`mul_bf16`, `div_bf16`, `sqrt_bf16`) retain V0 logic for compatibility and correctness reference. --- ## 2. BFloat16 Overview ![image](https://hackmd.io/_uploads/H1aDR9c6el.png) ![image](https://hackmd.io/_uploads/B1IdR5qpgg.png) * **add/sub**: align exponents → add/sub 8-bit mantissas (with hidden 1) → normalize → take 7-bit fraction * **mul**: multiply two 8-bit mantissas, adjust exponent by bias, normalize, then **RNE rounding via guard/sticky bits** * **div**: integer restoring division yields 7-bit fraction (truncate) * **sqrt**: binary search on mantissa; exponent adjusted for odd/even and halved --- ## 3. What Changed (V0 → V1) ### 1. main : Remove redundant “clear upper 16 bits” **V0 behavior**: after every call, `a0` was forced through ```asm slli a0, a0, 16 srli a0, a0, 16 ``` **Why redundant**: each subroutine already returns a properly masked 16-bit result, so the high bits are zero. **V1 action**: removed these unnecessary `slli/srli`. **Effect**: ~12 fewer dynamic instructions; reduced ALU shifts and pipeline stalls. --- ### 2. `add_bf16`: Leafified + Normalize via segmented shift **V0**: * Used `s*` registers and built a stack frame (`sw/lw` overhead). * Normalization after subtraction used a while-loop, shifting left bit-by-bit (up to 7 times). **V1**: * Uses only **`a*/t*`** → true **leaf function**, **no stack frame, zero `sw/lw`**. * Normalization replaced with **segmented shifting** (check bits 6..1 → shift 1–7 bits directly). Single-step adjustment reduces branches and loop iterations. **Result**: fewer branches and memory accesses; both **cycles** and **retired instructions** drop noticeably. :::spoiler <details> <summary>V0 full code</summary> ```asm ############################################################ # File : bf16_all_fixed.S # Target : RISC-V RV32I (Ripes-compatible) # Notes : 修正 bgt/ble 類助記詞、立即數溢位、label 對齊、16-bit 遮罩 # ★ 本版加入所有必要的 s* 保存/還原 (callee-saved) ############################################################ .data # 資料區 msg_suite_ok: .asciz "BF16 suite: PASS\n" msg_suite_fail: .asciz "BF16 suite: FAIL\n" msg_t1: .asciz "[OK ] f32_to_bf16\n" msg_t1f: .asciz "[FAIL] f32_to_bf16\n" msg_t2: .asciz "[OK ] bf16_to_f32\n" msg_t2f: .asciz "[FAIL] bf16_to_f32\n" msg_add: .asciz "[OK ] add_bf16\n" msg_addf: .asciz "[FAIL] add_bf16\n" msg_sub: .asciz "[OK ] sub_bf16\n" msg_subf: .asciz "[FAIL] sub_bf16\n" msg_mul: .asciz "[OK ] mul_bf16\n" msg_mulf: .asciz "[FAIL] mul_bf16\n" msg_div: .asciz "[OK ] div_bf16\n" msg_divf: .asciz "[FAIL] div_bf16\n" msg_sqrt: .asciz "[OK ] sqrt_bf16\n" msg_sqrtf: .asciz "[FAIL] sqrt_bf16\n" .text # 程式區 .globl main .globl f32_to_bf16 .globl bf16_to_f32 .globl add_bf16 .globl sub_bf16 .globl mul_bf16 .globl div_bf16 .globl sqrt_bf16 .globl mul_u16 #============================================================ # main:呼叫各函式做基本測試並列印 PASS/FAIL #============================================================ main: addi sp, sp, -16 # 建立簡易 stack frame sw ra, 12(sp) # 保存返回位址 li s0, 1 # s0 = suite_pass = 1 #------------------ T1: f32_to_bf16 ------------------ li a0, 0x3F800000 # 1.0f jal ra, f32_to_bf16 # -> 0x3F80 (bf16) slli a0, a0, 16 # 只比較低 16 srli a0, a0, 16 li t0, 0x3F80 bne a0, t0, print_t1_fail print_t1_ok: la a0, msg_t1 li a7, 4 ecall j t1_done print_t1_fail: la a0, msg_t1f li a7, 4 ecall li s0, 0 #------------------ T2: bf16_to_f32 ------------------ t1_done: li a0, 0x4000 # bf16(2.0) -> f32 0x40000000 jal ra, bf16_to_f32 li t0, 0x40000000 bne a0, t0, print_t2_fail print_t2_ok: la a0, msg_t2 li a7, 4 ecall j t2_done print_t2_fail: la a0, msg_t2f li a7, 4 ecall li s0, 0 #------------------ ADD: 1.0 + 1.0 = 2.0 ------------ t2_done: li a0, 0x3F80 # 1.0 li a1, 0x3F80 # 1.0 jal ra, add_bf16 # -> 2.0 (0x4000) slli a0, a0, 16 srli a0, a0, 16 li t0, 0x4000 bne a0, t0, print_add_fail print_add_ok: la a0, msg_add li a7, 4 ecall j add_done print_add_fail: la a0, msg_addf li a7, 4 ecall li s0, 0 #------------------ SUB: 2.0 - 1.0 = 1.0 ------------ add_done: li a0, 0x4000 # 2.0 li a1, 0x3F80 # 1.0 jal ra, sub_bf16 # -> 1.0 (0x3F80) slli a0, a0, 16 srli a0, a0, 16 li t0, 0x3F80 bne a0, t0, print_sub_fail print_sub_ok: la a0, msg_sub li a7, 4 ecall j sub_done print_sub_fail: la a0, msg_subf li a7, 4 ecall li s0, 0 #------------------ MUL: 1.5 * 2.0 = 3.0 ------------ sub_done: li a0, 0x3FC0 # 1.5 li a1, 0x4000 # 2.0 jal ra, mul_bf16 # -> 3.0 (0x4040) slli a0, a0, 16 srli a0, a0, 16 li t0, 0x4040 bne a0, t0, print_mul_fail print_mul_ok: la a0, msg_mul li a7, 4 ecall j mul_done print_mul_fail: la a0, msg_mulf li a7, 4 ecall li s0, 0 #------------------ DIV: 3.0 / 2.0 = 1.5 ------------ mul_done: li a0, 0x4040 # 3.0 li a1, 0x4000 # 2.0 jal ra, div_bf16 # -> 1.5 (0x3FC0) slli a0, a0, 16 srli a0, a0, 16 li t0, 0x3FC0 bne a0, t0, print_div_fail print_div_ok: la a0, msg_div li a7, 4 ecall j div_done print_div_fail: la a0, msg_divf li a7, 4 ecall li s0, 0 #------------------ SQRT: sqrt(4.0) = 2.0 ----------- div_done: li a0, 0x4080 # 4.0 jal ra, sqrt_bf16 # -> 2.0 (0x4000) slli a0, a0, 16 srli a0, a0, 16 li t0, 0x4000 bne a0, t0, print_sqrt_fail print_sqrt_ok: la a0, msg_sqrt li a7, 4 ecall j sqrt_done print_sqrt_fail: la a0, msg_sqrtf li a7, 4 ecall li s0, 0 #------------------ 總結 SUITE 結果 ------------------ sqrt_done: bnez s0, suite_ok suite_fail: la a0, msg_suite_fail li a7, 4 ecall j exit_suite suite_ok: la a0, msg_suite_ok li a7, 4 ecall exit_suite: lw ra, 12(sp) # ★ 對稱恢復 ra/sp(雖 ecall 10 不返回) addi sp, sp, 16 li a7, 10 ecall #============================================================ # f32_to_bf16 (leaf; 不使用 s*;不需保存) #============================================================ f32_to_bf16: srli t0, a0, 23 andi t0, t0, 0xFF li t1, 0xFF beq t0, t1, f32_is_special # exp==0xFF -> Inf/NaN srli t2, a0, 16 andi t2, t2, 1 li t3, 0x7FFF # 0.5 ULP add t2, t2, t3 add a0, a0, t2 srli a0, a0, 16 ret f32_is_special: srli a0, a0, 16 ret #============================================================ # bf16_to_f32 (leaf; 不使用 s*;不需保存) #============================================================ bf16_to_f32: slli a0, a0, 16 ret #============================================================ # add_bf16 (A + B) # 使用 s2,s3,s4,s5,s8,s9 → 需保存/還原 #============================================================ add_bf16: # ---------- Prologue: save s* ---------- addi sp, sp, -24 sw s8, 0(sp) sw s9, 4(sp) sw s2, 8(sp) sw s3, 12(sp) sw s4, 16(sp) sw s5, 20(sp) # 取 A/B 的 16-bit(高位清零) slli s8, a0, 16 # s8 = A srli s8, s8, 16 slli s9, a1, 16 # s9 = B srli s9, s9, 16 srli t0, s8, 15 # sign_a andi t0, t0, 1 srli t1, s9, 15 # sign_b andi t1, t1, 1 srli t2, s8, 7 # exp_a andi t2, t2, 0xFF srli t3, s9, 7 # exp_b andi t3, t3, 0xFF andi t4, s8, 0x7F # mant_a andi t5, s9, 0x7F # mant_b # 特殊值 li t6, 0xFF beq t2, t6, add_a_special beq t3, t6, add_b_special # 零值快速路徑 beq t2, x0, add_chk_a_zero j add_chk_b_zero add_chk_a_zero: beq t4, x0, add_ret_b # A == 0 -> 回傳 B add_chk_b_zero: beq t3, x0, add_chk_b_is_zero j add_norm add_chk_b_is_zero: beq t5, x0, add_ret_a # B == 0 -> 回傳 A add_norm: bnez t2, add_a_set1 j add_b_set1 add_a_set1: ori t4, t4, 0x80 # exp_a != 0 → 補隱含 1 add_b_set1: beqz t3, add_align # exp_b == 0 → 不補 1 ori t5, t5, 0x80 # exp_b != 0 → 補隱含 1 j add_align add_align: sub s2, t2, t3 # s2 = exp_diff blt x0, s2, add_a_bigger # s2 > 0 blt s2, x0, add_b_bigger # s2 < 0 add s3, x0, t2 # 指數相等 j add_op add_a_bigger: add s3, x0, t2 li t6, 8 blt t6, s2, add_ret_a # 差距 > 8 → 回傳 A srl t5, t5, s2 # B 尾數右移對齊 j add_op add_b_bigger: add s3, x0, t3 li t6, -8 blt s2, t6, add_ret_b # 差距 < -8 → 回傳 B sub t6, x0, s2 # t6 = -s2 srl t4, t4, t6 # A 尾數右移對齊 add_op: beq t0, t1, add_same_sign # 同號 → 相加 # 異號 → 相減 bge t4, t5, add_a_ge_b add s4, x0, t1 # 結果符號 = B sub s5, t5, t4 # mant_res = B - A j add_norm_diff add_a_ge_b: add s4, x0, t0 # 結果符號 = A sub s5, t4, t5 # mant_res = A - B add_norm_diff: beq s5, x0, add_ret_zero # 結果 0 add_norm_loop: andi t6, s5, 0x80 bnez t6, add_pack # 已規範化 addi s3, s3, -1 bge x0, s3, add_ret_zero # 指數 <= 0 → 0 slli s5, s5, 1 j add_norm_loop add_same_sign: add s4, x0, t0 add s5, t4, t5 andi t6, s5, 0x100 beq t6, x0, add_pack srli s5, s5, 1 addi s3, s3, 1 li t6, 0xFF bge s3, t6, add_to_inf add_pack: slli s4, s4, 15 andi s3, s3, 0xFF slli s3, s3, 7 andi s5, s5, 0x7F or a0, s4, s3 or a0, a0, s5 j add_epilogue add_to_inf: slli s4, s4, 15 li t6, 0x7F80 or a0, s4, t6 j add_epilogue add_ret_zero: li a0, 0x0000 j add_epilogue add_ret_a: add a0, x0, s8 j add_epilogue add_ret_b: add a0, x0, s9 j add_epilogue add_a_special: bnez t4, add_ret_a # A=NaN → A beq t3, t6, add_both_inf_nan add a0, x0, s8 # A=Inf、B 非特殊 → A j add_epilogue add_both_inf_nan: bnez t5, add_ret_b # B=NaN → B beq t0, t1, add_ret_b # 同號 Inf → B li a0, 0x7FC0 # +Inf + (-Inf) = NaN j add_epilogue add_b_special: bnez t5, add_ret_b # B=NaN → B add a0, x0, s9 # B=Inf → B j add_epilogue add_epilogue: # ---------- Epilogue: restore s* ---------- lw s5, 20(sp) lw s4, 16(sp) lw s3, 12(sp) lw s2, 8(sp) lw s9, 4(sp) lw s8, 0(sp) addi sp, sp, 24 ret #============================================================ # sub_bf16 (A - B) = A + (-B) # 只需保存 ra(caller-saved),本函式會呼叫 add_bf16 #============================================================ sub_bf16: addi sp, sp, -4 sw ra, 0(sp) li t6, 0x8000 xor a1, a1, t6 # 反轉 B 的符號 jal ra, add_bf16 lw ra, 0(sp) addi sp, sp, 4 ret #============================================================ # mul_bf16 (A * B) — 含捨入;使用 s1,s2,s3,s4,s8,s9,s10;呼叫 mul_u16 #============================================================ mul_bf16: # ---------- Prologue ---------- addi sp, sp, -32 sw ra, 0(sp) sw s1, 4(sp) sw s2, 8(sp) sw s3, 12(sp) sw s4, 16(sp) sw s8, 20(sp) sw s9, 24(sp) sw s10,28(sp) # 取 A/B 的 16-bit slli s8, a0, 16 srli s8, s8, 16 slli s9, a1, 16 srli s9, s9, 16 srli t0, s8, 15 # sign_a andi t0, t0, 1 srli t1, s9, 15 # sign_b andi t1, t1, 1 xor s1, t0, t1 # result_sign srli t2, s8, 7 # exp_a andi t2, t2, 0xFF srli t3, s9, 7 # exp_b andi t3, t3, 0xFF andi t4, s8, 0x7F # mant_a andi t5, s9, 0x7F # mant_b li t6, 0xFF beq t2, t6, mul_a_special beq t3, t6, mul_b_special beq t2, x0, mul_chk_a_zero beq t3, x0, mul_chk_b_zero j mul_norm_go mul_chk_a_zero: beqz t4, mul_zero j mul_norm_go mul_chk_b_zero: beqz t5, mul_zero mul_norm_go: addi s2, x0, 0 bnez t2, mul_a_ok beqz t4, mul_zero mul_a_den_loop: andi t6, t4, 0x80 bnez t6, mul_a_normed slli t4, t4, 1 addi s2, s2, -1 j mul_a_den_loop mul_a_normed: addi t2, x0, 1 j mul_b_chk mul_a_ok: ori t4, t4, 0x80 mul_b_chk: bnez t3, mul_b_ok beqz t5, mul_zero mul_b_den_loop: andi t6, t5, 0x80 bnez t6, mul_b_normed slli t5, t5, 1 addi s2, s2, -1 j mul_b_den_loop mul_b_normed: addi t3, x0, 1 j mul_do mul_b_ok: ori t5, t5, 0x80 mul_do: # 直接呼叫(已在 prologue 保存 ra) add a0, x0, t4 add a1, x0, t5 jal ra, mul_u16 # a0 = mant_a * mant_b add s3, x0, a0 # s3 = mant_prod add s4, t2, t3 addi s4, s4, -127 add s4, s4, s2 li t6, 0x8000 and t6, s3, t6 beqz t6, mul_norm_lt2 # >= 2.0 mul_norm_ge2: addi s4, s4, 1 srli s10, s3, 8 andi t0, s10, 0x7F srli t1, s3, 7 andi t1, t1, 1 andi t2, s3, 0x7F j mul_round # [1,2] mul_norm_lt2: srli s10, s3, 7 andi t0, s10, 0x7F srli t1, s3, 6 andi t1, t1, 1 andi t2, s3, 0x3F mul_round: beqz t1, mul_after_round bnez t2, mul_do_round andi t3, t0, 1 beqz t3, mul_after_round mul_do_round: addi t0, t0, 1 li t3, 0x80 bne t0, t3, mul_after_round li t0, 0x00 addi s4, s4, 1 mul_after_round: li t6, 0xFF bge s4, t6, mul_to_inf bge x0, s4, mul_underflow slli s1, s1, 15 andi s4, s4, 0xFF slli s4, s4, 7 andi t0, t0, 0x7F or a0, s1, s4 or a0, a0, t0 j mul_epilogue mul_to_inf: slli s1, s1, 15 li t6, 0x7F80 or a0, s1, t6 j mul_epilogue mul_underflow: slli a0, s1, 15 j mul_epilogue mul_zero: slli a0, s1, 15 j mul_epilogue mul_a_special: bnez t4, mul_ret_a beq t3, x0, mul_inf_times_zero slli s1, s1, 15 li t6, 0x7F80 or a0, s1, t6 j mul_epilogue mul_b_special: bnez t5, mul_ret_b beq t2, x0, mul_inf_times_zero slli s1, s1, 15 li t6, 0x7F80 or a0, s1, t6 j mul_epilogue mul_inf_times_zero: li a0, 0x7FC0 j mul_epilogue mul_ret_a: add a0, x0, s8 j mul_epilogue mul_ret_b: add a0, x0, s9 j mul_epilogue mul_epilogue: lw s10,28(sp) lw s9, 24(sp) lw s8, 20(sp) lw s4, 16(sp) lw s3, 12(sp) lw s2, 8(sp) lw s1, 4(sp) lw ra, 0(sp) addi sp, sp, 32 ret #============================================================ # div_bf16 (A / B) # 使用 s1,s2,s3,s4,s5,s8,s9,s10;leaf(不呼叫他人)→ 無需保存 ra #============================================================ div_bf16: # ---------- Prologue ---------- addi sp, sp, -32 sw s1, 0(sp) sw s2, 4(sp) sw s3, 8(sp) sw s4, 12(sp) sw s5, 16(sp) sw s8, 20(sp) sw s9, 24(sp) sw s10,28(sp) # 取 A/B 的 16-bit slli s8, a0, 16 srli s8, s8, 16 slli s9, a1, 16 srli s9, s9, 16 srli t0, s8, 15 # sign_a andi t0, t0, 1 srli t1, s9, 15 # sign_b andi t1, t1, 1 xor s1, t0, t1 # result_sign srli t2, s8, 7 # exp_a andi t2, t2, 0xFF srli t3, s9, 7 # exp_b andi t3, t3, 0xFF andi t4, s8, 0x7F # mant_a andi t5, s9, 0x7F # mant_b li t6, 0xFF beq t3, t6, div_b_special beq t3, x0, div_b_zero_or_subn beq t2, t6, div_a_special beq t2, x0, div_a_zero_or_subn div_common: bnez t2, div_a_set1 beqz t4, div_result_zero div_a_set1: ori t4, t4, 0x80 bnez t3, div_b_set1 beqz t5, div_result_nan div_b_set1: ori t5, t5, 0x80 # 長除法 slli s2, t4, 15 add s3, x0, t5 li s4, 0 li s5, 0 div_loop_i: li t6, 16 bge s5, t6, div_qdone slli s4, s4, 1 li t6, 15 sub t6, t6, s5 sll t6, s3, t6 bgeu s2, t6, div_sub j div_next div_sub: sub s2, s2, t6 ori s4, s4, 1 div_next: addi s5, s5, 1 j div_loop_i div_qdone: sub s5, t2, t3 addi s5, s5, 127 beqz t2, div_adj_a j div_adj_b div_adj_a: addi s5, s5, -1 div_adj_b: beqz t3, div_adj_b2 j div_norm div_adj_b2: addi s5, s5, 1 div_norm: li t6, 0x8000 and t6, s4, t6 bnez t6, div_q_has1 div_q_shift: li t6, 1 bge t6, s5, div_q_done # if s5 <= 1 li s10, 0x8000 and s10, s4, s10 bnez s10, div_q_done slli s4, s4, 1 addi s5, s5, -1 j div_q_shift div_q_has1: srli s4, s4, 8 div_q_done: andi s4, s4, 0x7F li t6, 0xFF bge s5, t6, div_to_inf bge x0, s5, div_result_zero slli s1, s1, 15 andi s5, s5, 0xFF slli s5, s5, 7 or a0, s1, s5 or a0, a0, s4 j div_epilogue div_b_special: bnez t5, div_ret_b slli a0, s1, 15 j div_epilogue div_b_zero_or_subn: beqz t5, div_by_zero j div_common div_by_zero: beq t2, x0, div_result_nan slli s1, s1, 15 li t6, 0x7F80 or a0, s1, t6 j div_epilogue div_a_special: bnez t4, div_ret_a li t6, 0xFF beq t3, t6, div_result_nan slli s1, s1, 15 li t6, 0x7F80 or a0, s1, t6 j div_epilogue div_a_zero_or_subn: beqz t4, div_result_zero j div_common div_result_zero: slli a0, s1, 15 j div_epilogue div_result_nan: li a0, 0x7FC0 j div_epilogue div_to_inf: slli s1, s1, 15 li t6, 0x7F80 or a0, s1, t6 j div_epilogue div_ret_a: add a0, x0, s8 j div_epilogue div_ret_b: add a0, x0, s9 j div_epilogue div_epilogue: lw s10,28(sp) lw s9, 24(sp) lw s8, 20(sp) lw s5, 16(sp) lw s4, 12(sp) lw s3, 8(sp) lw s2, 4(sp) lw s1, 0(sp) addi sp, sp, 32 ret #============================================================ # sqrt_bf16 (sqrt(A)) # 使用 s2,s3,s4,s5,s6,s7,s8;呼叫 mul_u16 → 另需保存 ra #============================================================ sqrt_bf16: # ---------- Prologue ---------- addi sp, sp, -32 sw ra, 0(sp) sw s2, 4(sp) sw s3, 8(sp) sw s4, 12(sp) sw s5, 16(sp) sw s6, 20(sp) sw s7, 24(sp) sw s8, 28(sp) # A(16) : 取 a0 的低 16 位到 s8(上 16 清 0) slli s8, a0, 16 srli s8, s8, 16 srli t0, s8, 15 # sign andi t0, t0, 1 srli t1, s8, 7 # exp andi t1, t1, 0xFF andi t2, s8, 0x7F # mant li t3, 0xFF bne t1, t3, sqrt_chk_zero bnez t2, sqrt_ret_a # NaN → A bnez t0, sqrt_nan # -Inf → NaN add a0, x0, s8 # +Inf → A j sqrt_epilogue sqrt_chk_zero: beq t1, x0, sqrt_zero # 0/subnormal → 0 bnez t0, sqrt_nan # 負數 → NaN addi s2, t1, -127 ori s3, t2, 0x80 andi t3, s2, 1 beqz t3, sqrt_even slli s3, s3, 1 addi s2, s2, -1 sqrt_even: srai s4, s2, 1 addi s4, s4, 127 li s5, 90 # low li s6, 256 # high li s7, 128 # result sqrt_bs_loop: blt s6, s5, sqrt_bs_done # if low > high add t3, s5, s6 srli t3, t3, 1 # mid # 呼叫 mul_u16(已保存 ra) add a0, x0, t3 add a1, x0, t3 jal ra, mul_u16 srli a0, a0, 7 # sq = (mid^2)/128 blt s3, a0, sqrt_sq_too_big # if m < sq add s7, x0, t3 addi s5, t3, 1 j sqrt_bs_loop sqrt_sq_too_big: addi s6, t3, -1 j sqrt_bs_loop sqrt_bs_done: li t3, 256 blt s7, t3, sqrt_norm_low_ok srli s7, s7, 1 addi s4, s4, 1 sqrt_norm_low_ok: li t3, 128 bge s7, t3, sqrt_pack sqrt_shift_up: li t3, 1 bge t3, s4, sqrt_pack # if s4 <= 1 slli s7, s7, 1 addi s4, s4, -1 blt s7, t3, sqrt_shift_up sqrt_pack: andi s7, s7, 0x7F li t3, 0xFF bge s4, t3, sqrt_to_inf bge x0, s4, sqrt_zero slli s4, s4, 7 or a0, s4, s7 j sqrt_epilogue sqrt_zero: li a0, 0x0000 j sqrt_epilogue sqrt_nan: li a0, 0x7FC0 j sqrt_epilogue sqrt_to_inf: li a0, 0x7F80 j sqrt_epilogue sqrt_ret_a: add a0, x0, s8 j sqrt_epilogue sqrt_epilogue: lw s8, 28(sp) lw s7, 24(sp) lw s6, 20(sp) lw s5, 16(sp) lw s4, 12(sp) lw s3, 8(sp) lw s2, 4(sp) lw ra, 0(sp) addi sp, sp, 32 ret #============================================================ # mul_u16:無號 16-bit 乘法(RV32I:移位+加法)(leaf) #============================================================ mul_u16: li t0, 0 mul16_loop: beq a1, x0, mul16_done andi t1, a1, 1 beq t1, x0, mul16_skip_add add t0, t0, a0 mul16_skip_add: slli a0, a0, 1 srli a1, a1, 1 j mul16_loop mul16_done: add a0, x0, t0 ret ``` </details> ::: :::spoiler <details> <summary>V1 full code</summary> ```asm .data .text msg_suite_ok: .asciz "BF16 suite: PASS\n" msg_suite_fail: .asciz "BF16 suite: FAIL\n" msg_t1: .asciz "[OK ] f32_to_bf16\n" msg_t1f: .asciz "[FAIL] f32_to_bf16\n" msg_t2: .asciz "[OK ] bf16_to_f32\n" msg_t2f: .asciz "[FAIL] bf16_to_f32\n" msg_add: .asciz "[OK ] add_bf16\n" msg_addf: .asciz "[FAIL] add_bf16\n" msg_sub: .asciz "[OK ] sub_bf16\n" msg_subf: .asciz "[FAIL] sub_bf16\n" msg_mul: .asciz "[OK ] mul_bf16\n" msg_mulf: .asciz "[FAIL] mul_bf16\n" msg_div: .asciz "[OK ] div_bf16\n" msg_divf: .asciz "[FAIL] div_bf16\n" msg_sqrt: .asciz "[OK ] sqrt_bf16\n" msg_sqrtf: .asciz "[FAIL] sqrt_bf16\n" # 程式區 .globl main .globl f32_to_bf16 .globl bf16_to_f32 .globl add_bf16 .globl sub_bf16 .globl mul_bf16 .globl div_bf16 .globl sqrt_bf16 .globl mul_u16 # 呼叫各函式做基本測試並列印 PASS/FAIL main: addi sp, sp, -16 # 簡易堆疊空間 sw ra, 12(sp) # 保存返回位址 li s0, 1 # s0 = suite_pass = 1 # T1: f32_to_bf16 li a0, 0x3F800000 # 1.0f jal ra, f32_to_bf16 # -> 0x3F80 (bf16) slli a0, a0, 16 # 清除高 16 → 只保留低 16 srli a0, a0, 16 li t0, 0x3F80 bne a0, t0, print_t1_fail print_t1_ok: la a0, msg_t1 li a7, 4 ecall j t1_done print_t1_fail: la a0, msg_t1f li a7, 4 ecall li s0, 0 # T2: bf16_to_f32 t1_done: li a0, 0x4000 # bf16(2.0) -> f32 0x40000000 jal ra, bf16_to_f32 li t0, 0x40000000 bne a0, t0, print_t2_fail print_t2_ok: la a0, msg_t2 li a7, 4 ecall j t2_done print_t2_fail: la a0, msg_t2f li a7, 4 ecall li s0, 0 # ADD: 1.0 + 1.0 = 2.0 t2_done: li a0, 0x3F80 # 1.0 li a1, 0x3F80 # 1.0 jal ra, add_bf16 # -> 2.0 (0x4000) slli a0, a0, 16 # 清除高 16 srli a0, a0, 16 li t0, 0x4000 bne a0, t0, print_add_fail print_add_ok: la a0, msg_add li a7, 4 ecall j add_done print_add_fail: la a0, msg_addf li a7, 4 ecall li s0, 0 # SUB: 2.0 - 1.0 = 1.0 add_done: li a0, 0x4000 # 2.0 li a1, 0x3F80 # 1.0 jal ra, sub_bf16 # -> 1.0 (0x3F80) slli a0, a0, 16 srli a0, a0, 16 li t0, 0x3F80 bne a0, t0, print_sub_fail print_sub_ok: la a0, msg_sub li a7, 4 ecall j sub_done print_sub_fail: la a0, msg_subf li a7, 4 ecall li s0, 0 #MUL: 1.5 * 2.0 = 3.0 sub_done: li a0, 0x3FC0 # 1.5 li a1, 0x4000 # 2.0 jal ra, mul_bf16 # -> 3.0 (0x4040) slli a0, a0, 16 srli a0, a0, 16 li t0, 0x4040 bne a0, t0, print_mul_fail print_mul_ok: la a0, msg_mul li a7, 4 ecall j mul_done print_mul_fail: la a0, msg_mulf li a7, 4 ecall li s0, 0 # DIV: 3.0 / 2.0 = 1.5 mul_done: li a0, 0x4040 # 3.0 li a1, 0x4000 # 2.0 jal ra, div_bf16 # -> 1.5 (0x3FC0) slli a0, a0, 16 srli a0, a0, 16 li t0, 0x3FC0 bne a0, t0, print_div_fail print_div_ok: la a0, msg_div li a7, 4 ecall j div_done print_div_fail: la a0, msg_divf li a7, 4 ecall li s0, 0 # SQRT: sqrt(4.0) = 2.0 div_done: li a0, 0x4080 # 4.0 jal ra, sqrt_bf16 # -> 2.0 (0x4000) slli a0, a0, 16 srli a0, a0, 16 li t0, 0x4000 bne a0, t0, print_sqrt_fail print_sqrt_ok: la a0, msg_sqrt li a7, 4 ecall j sqrt_done print_sqrt_fail: la a0, msg_sqrtf li a7, 4 ecall li s0, 0 #SUITE sqrt_done: bnez s0, suite_ok suite_fail: la a0, msg_suite_fail li a7, 4 ecall j exit_suite suite_ok: la a0, msg_suite_ok li a7, 4 ecall exit_suite: li a7, 10 ecall ############################################# # f32_to_bf16 ############################################# f32_to_bf16: srli t0, a0, 23 # t0 = 指數 (8位) andi t0, t0, 0xFF li t1, 0xFF beq t0, t1, f32_is_special # exp==0xFF -> Inf/NaN srli t2, a0, 16 # t2 = (a0 >> 16) andi t2, t2, 1 # t2 = LSB for ties li t3, 0x7FFF # 0.5 ULP 偏移 add t2, t2, t3 add a0, a0, t2 srli a0, a0, 16 # 取高16位作 bf16 ret f32_is_special: srli a0, a0, 16 # 直接取高16位(保留 NaN/Inf payload) ret ############################################# # bf16_to_f32 ############################################# bf16_to_f32: slli a0, a0, 16 # bf16 << 16 ret ############################################# # add_bf16 (A + B) ############################################# add_bf16: #把低 16 位搬到高半(高半乾淨)邏輯右移回來 → 上 16 直接清成 0 slli s8, a0, 16 # A srli s8, s8, 16 slli s9, a1, 16 # B srli s9, s9, 16 srli t0, s8, 15 # sign_a andi t0, t0, 1 srli t1, s9, 15 # sign_b andi t1, t1, 1 srli t2, s8, 7 # exp_a andi t2, t2, 0xFF srli t3, s9, 7 # exp_b andi t3, t3, 0xFF andi t4, s8, 0x7F # mant_a andi t5, s9, 0x7F # mant_b # 特殊值 li t6, 0xFF beq t2, t6, add_a_special beq t3, t6, add_b_special # 零值快速路徑 beq t2, x0, add_chk_a_zero j add_chk_b_zero add_chk_a_zero: beq t4, x0, add_ret_b # A == 0 -> 回傳 B add_chk_b_zero: beq t3, x0, add_chk_b_is_zero j add_norm add_chk_b_is_zero: beq t5, x0, add_ret_a # B == 0 -> 回傳 A add_norm: bnez t2, add_a_set1 j add_b_set1 add_a_set1: ori t4, t4, 0x80 # A 非0 -> 補隱含1 add_b_set1: beqz t3, add_align #exp_b != 0 要補隱含 1;exp_b == 0 不補 ori t5, t5, 0x80 # B 非0 -> 補隱含1 j add_align add_align: sub s2, t2, t3 # s2 = exp_diff blt x0, s2, add_a_bigger # if s2 > 0 blt s2, x0, add_b_bigger # if s2 < 0 add s3, x0, t2 # 指數相等 j add_op add_a_bigger: add s3, x0, t2 li t6, 8 blt t6, s2, add_ret_a # if s2 > 8 srl t5, t5, s2 # B 尾數右移對齊 j add_op add_b_bigger: add s3, x0, t3 li t6, -8 blt s2, t6, add_ret_b # if s2 < -8 neg t6, s2 srl t4, t4, t6 # A 尾數右移對齊 add_op: beq t0, t1, add_same_sign # 同號 => 相加 # 異號 => 相減 bge t4, t5, add_a_ge_b add s4, x0, t1 # 結果符號 = B 的符號 sub s5, t5, t4 # mant_res = B - A j add_norm_diff add_a_ge_b: add s4, x0, t0 # 結果符號 = A 的符號 sub s5, t4, t5 # mant_res = A - B add_norm_diff: beq s5, x0, add_ret_zero # 差值為0 => 0 add_norm_loop: andi t6, s5, 0x80 bnez t6, add_pack # 已規範化 addi s3, s3, -1 # 指數-- bge x0, s3, add_ret_zero # if s3 <= 0 slli s5, s5, 1 j add_norm_loop add_same_sign: add s4, x0, t0 # 結果符號 = 同號 add s5, t4, t5 # mant_res = A + B andi t6, s5, 0x100 beq t6, x0, add_pack srli s5, s5, 1 # 進位:尾數右移 addi s3, s3, 1 # 指數+1 li t6, 0xFF bge s3, t6, add_to_inf # 上溢 => Inf add_pack: slli s4, s4, 15 # 符號到 bit15 andi s3, s3, 0xFF slli s3, s3, 7 # 指數到 bit14:7 andi s5, s5, 0x7F # 尾數 7-bit or a0, s4, s3 or a0, a0, s5 ret add_to_inf: slli s4, s4, 15 li t6, 0x7F80 or a0, s4, t6 ret add_ret_zero: li a0, 0x0000 ret add_ret_a: add a0, x0, s8 ret add_ret_b: add a0, x0, s9 ret add_a_special: bnez t4, add_ret_a # A 是 NaN -> 傳回 A beq t3, t6, add_both_inf_nan add a0, x0, s8 # A 是 Inf、B 非特殊 -> 回傳 A ret add_both_inf_nan: bnez t5, add_ret_b # B 是 NaN -> 回傳 B beq t0, t1, add_ret_b # Inf 同號 -> 回傳 B li a0, 0x7FC0 # +Inf + (-Inf) = NaN ret add_b_special: bnez t5, add_ret_b # B 是 NaN -> 回傳 B add a0, x0, s9 # B 是 Inf -> 回傳 B ret ############################################# # sub_bf16 (A - B) = A + (-B) ############################################# sub_bf16: li t6, 0x8000 # 建立符號遮罩(避免 xori 12-bit 溢位) xor a1, a1, t6 # 反轉 B 的符號位 jal ra, add_bf16 # A + (-B) ret #============================================================ # mul_bf16 (A * B) — 內聯 8x8 乘法 + RNE 捨入 # 使用 s1,s2,s3,s4,s8,s9,s10;不再呼叫 mul_u16 #============================================================ mul_bf16: addi sp, sp, -32 sw ra, 0(sp) sw s1, 4(sp) sw s2, 8(sp) sw s3, 12(sp) sw s4, 16(sp) sw s8, 20(sp) sw s9, 24(sp) sw s10,28(sp) slli s8, a0, 16 # A srli s8, s8, 16 slli s9, a1, 16 # B srli s9, s9, 16 srli t0, s8, 15 # sign_a andi t0, t0, 1 srli t1, s9, 15 # sign_b andi t1, t1, 1 xor s1, t0, t1 # result_sign srli t2, s8, 7 # exp_a andi t2, t2, 0xFF srli t3, s9, 7 # exp_b andi t3, t3, 0xFF andi t4, s8, 0x7F # mant_a andi t5, s9, 0x7F # mant_b li t6, 0xFF beq t2, t6, mul_a_special beq t3, t6, mul_b_special beq t2, x0, mul_chk_a_zero beq t3, x0, mul_chk_b_zero j mul_norm_go mul_chk_a_zero: beqz t4, mul_zero j mul_norm_go mul_chk_b_zero: beqz t5, mul_zero mul_norm_go: addi s2, x0, 0 bnez t2, mul_a_ok beqz t4, mul_zero mul_a_den_loop: andi t6, t4, 0x80 bnez t6, mul_a_normed slli t4, t4, 1 addi s2, s2, -1 j mul_a_den_loop mul_a_normed: addi t2, x0, 1 j mul_b_chk mul_a_ok: ori t4, t4, 0x80 mul_b_chk: bnez t3, mul_b_ok beqz t5, mul_zero mul_b_den_loop: andi t6, t5, 0x80 bnez t6, mul_b_normed slli t5, t5, 1 addi s2, s2, -1 j mul_b_den_loop mul_b_normed: addi t3, x0, 1 j mul_do mul_b_ok: ori t5, t5, 0x80 mul_do: # ---- 內聯 8x8 乘法:s3 = t4 * t5 ---- li s3, 0 add t6, x0, t5 # multiplier add s10, x0, t4 # multiplicand li t0, 8 mul8_loop: andi t1, t6, 1 beqz t1, mul8_skip_add add s3, s3, s10 mul8_skip_add: slli s10, s10, 1 srli t6, t6, 1 addi t0, t0, -1 bnez t0, mul8_loop # result_exp = exp_a + exp_b - 127 + exp_adjust add s4, t2, t3 addi s4, s4, -127 add s4, s4, s2 # 正規化 + RNE li t6, 0x8000 and t6, s3, t6 beqz t6, mul_norm_lt2 # >= 2.0 mul_norm_ge2: addi s4, s4, 1 srli s10, s3, 8 # m8 andi t0, s10, 0x7F # frac7 srli t1, s3, 7 # guard andi t1, t1, 1 andi t2, s3, 0x7F # sticky j mul_round # [1,2) mul_norm_lt2: srli s10, s3, 7 # m8 andi t0, s10, 0x7F # frac7 srli t1, s3, 6 # guard andi t1, t1, 1 andi t2, s3, 0x3F # sticky mul_round: beqz t1, mul_after_round # guard=0 → 不進位 bnez t2, mul_do_round # sticky!=0 → 進位 andi t3, t0, 1 # tie: LSB=1 才進位(最近偶數) beqz t3, mul_after_round mul_do_round: addi t0, t0, 1 li t3, 0x80 bne t0, t3, mul_after_round li t0, 0x00 addi s4, s4, 1 mul_after_round: li t6, 0xFF bge s4, t6, mul_to_inf bge x0, s4, mul_underflow slli s1, s1, 15 andi s4, s4, 0xFF slli s4, s4, 7 andi t0, t0, 0x7F or a0, s1, s4 or a0, a0, t0 j mul_epilogue mul_to_inf: slli s1, s1, 15 li t6, 0x7F80 or a0, s1, t6 j mul_epilogue mul_underflow: slli a0, s1, 15 j mul_epilogue mul_zero: slli a0, s1, 15 j mul_epilogue mul_a_special: bnez t4, mul_ret_a beq t3, x0, mul_inf_times_zero slli s1, s1, 15 li t6, 0x7F80 or a0, s1, t6 j mul_epilogue mul_b_special: bnez t5, mul_ret_b beq t2, x0, mul_inf_times_zero slli s1, s1, 15 li t6, 0x7F80 or a0, s1, t6 j mul_epilogue mul_inf_times_zero: li a0, 0x7FC0 j mul_epilogue mul_ret_a: add a0, x0, s8 j mul_epilogue mul_ret_b: add a0, x0, s9 j mul_epilogue mul_epilogue: lw s10,28(sp) lw s9, 24(sp) lw s8, 20(sp) lw s4, 16(sp) lw s3, 12(sp) lw s2, 8(sp) lw s1, 4(sp) lw ra, 0(sp) addi sp, sp, 32 ret ######################################## # div_bf16 (A / B) ######################################## div_bf16: #把低 16 位搬到高半(高半乾淨)邏輯右移回來 → 上 16 直接清成 0 slli s8, a0, 16 # A srli s8, s8, 16 slli s9, a1, 16 # B srli s9, s9, 16 srli t0, s8, 15 # sign_a andi t0, t0, 1 srli t1, s9, 15 # sign_b andi t1, t1, 1 xor s1, t0, t1 # result_sign srli t2, s8, 7 # exp_a andi t2, t2, 0xFF srli t3, s9, 7 # exp_b andi t3, t3, 0xFF andi t4, s8, 0x7F # mant_a andi t5, s9, 0x7F # mant_b li t6, 0xFF beq t3, t6, div_b_special beq t3, x0, div_b_zero_or_subn beq t2, t6, div_a_special beq t2, x0, div_a_zero_or_subn div_common: bnez t2, div_a_set1 beqz t4, div_result_zero div_a_set1: ori t4, t4, 0x80 bnez t3, div_b_set1 beqz t5, div_result_nan div_b_set1: ori t5, t5, 0x80 # 長除法 slli s2, t4, 15 add s3, x0, t5 li s4, 0 li s5, 0 div_loop_i: li t6, 16 bge s5, t6, div_qdone slli s4, s4, 1 li t6, 15 sub t6, t6, s5 sll t6, s3, t6 bgeu s2, t6, div_sub j div_next div_sub: sub s2, s2, t6 ori s4, s4, 1 div_next: addi s5, s5, 1 j div_loop_i div_qdone: sub s5, t2, t3 addi s5, s5, 127 beqz t2, div_adj_a j div_adj_b div_adj_a: addi s5, s5, -1 div_adj_b: beqz t3, div_adj_b2 j div_norm div_adj_b2: addi s5, s5, 1 div_norm: li t6, 0x8000 and t6, s4, t6 bnez t6, div_q_has1 div_q_shift: li t6, 1 bge t6, s5, div_q_done # if s5 <= 1 li s10, 0x8000 and s10, s4, s10 bnez s10, div_q_done slli s4, s4, 1 addi s5, s5, -1 j div_q_shift div_q_has1: srli s4, s4, 8 div_q_done: andi s4, s4, 0x7F li t6, 0xFF bge s5, t6, div_to_inf bge x0, s5, div_result_zero slli s1, s1, 15 andi s5, s5, 0xFF slli s5, s5, 7 or a0, s1, s5 or a0, a0, s4 ret div_b_special: bnez t5, div_ret_b slli a0, s1, 15 ret div_b_zero_or_subn: beqz t5, div_by_zero j div_common div_by_zero: beq t2, x0, div_result_nan slli s1, s1, 15 li t6, 0x7F80 or a0, s1, t6 ret div_a_special: bnez t4, div_ret_a li t6, 0xFF beq t3, t6, div_result_nan slli s1, s1, 15 li t6, 0x7F80 or a0, s1, t6 ret div_a_zero_or_subn: beqz t4, div_result_zero j div_common div_result_zero: slli a0, s1, 15 ret div_result_nan: li a0, 0x7FC0 ret div_to_inf: slli s1, s1, 15 li t6, 0x7F80 or a0, s1, t6 ret div_ret_a: add a0, x0, s8 ret div_ret_b: add a0, x0, s9 ret #============================================================ # sqrt_bf16 (sqrt(A)) # 使用 s2,s3,s4,s5,s6,s7,s8;二分法內聯 mid*mid(9步乘法) #============================================================ sqrt_bf16: addi sp, sp, -32 sw ra, 0(sp) sw s2, 4(sp) sw s3, 8(sp) sw s4, 12(sp) sw s5, 16(sp) sw s6, 20(sp) sw s7, 24(sp) sw s8, 28(sp) slli s8, a0, 16 # A(16) 低位 → s8 srli s8, s8, 16 srli t0, s8, 15 # sign andi t0, t0, 1 srli t1, s8, 7 # exp andi t1, t1, 0xFF andi t2, s8, 0x7F # mant li t3, 0xFF bne t1, t3, sqrt_chk_zero bnez t2, sqrt_ret_a # NaN → A bnez t0, sqrt_nan # -Inf → NaN add a0, x0, s8 # +Inf → A j sqrt_epilogue sqrt_chk_zero: beq t1, x0, sqrt_zero # 0 / 次正規 → 0 bnez t0, sqrt_nan # 負 → NaN addi s2, t1, -127 # e = exp - bias ori s3, t2, 0x80 # m = 1.x andi t3, s2, 1 beqz t3, sqrt_even slli s3, s3, 1 # odd exponent: m <<= 1, e-- addi s2, s2, -1 sqrt_even: srai s4, s2, 1 addi s4, s4, 127 # new_exp li s5, 90 # low li s6, 256 # high li s7, 128 # result sqrt_bs_loop: blt s6, s5, sqrt_bs_done # low > high ? add t3, s5, s6 srli t3, t3, 1 # mid in t3 # ---- 內聯 mid*mid(9步移位加法;mid ∈ [0..256])---- add t4, x0, t3 # multiplicand add t5, x0, t3 # multiplier li t6, 0 # acc = 0 li t0, 9 sqrt_mul9_loop: andi t1, t5, 1 beqz t1, sqrt_mul9_skip add t6, t6, t4 sqrt_mul9_skip: slli t4, t4, 1 srli t5, t5, 1 addi t0, t0, -1 bnez t0, sqrt_mul9_loop srli a0, t6, 7 # sq = (mid^2)/128 blt s3, a0, sqrt_sq_too_big # if m < sq → high = mid - 1 add s7, x0, t3 # result = mid addi s5, t3, 1 # low = mid + 1 j sqrt_bs_loop sqrt_sq_too_big: addi s6, t3, -1 j sqrt_bs_loop sqrt_bs_done: li t3, 256 blt s7, t3, sqrt_norm_low_ok srli s7, s7, 1 addi s4, s4, 1 sqrt_norm_low_ok: li t3, 128 bge s7, t3, sqrt_pack sqrt_shift_up: li t3, 1 bge t3, s4, sqrt_pack slli s7, s7, 1 addi s4, s4, -1 blt s7, t3, sqrt_shift_up sqrt_pack: andi s7, s7, 0x7F li t3, 0xFF bge s4, t3, sqrt_to_inf bge x0, s4, sqrt_zero slli s4, s4, 7 or a0, s4, s7 j sqrt_epilogue sqrt_zero: li a0, 0x0000 j sqrt_epilogue sqrt_nan: li a0, 0x7FC0 j sqrt_epilogue sqrt_to_inf: li a0, 0x7F80 j sqrt_epilogue sqrt_ret_a: add a0, x0, s8 j sqrt_epilogue sqrt_epilogue: lw s8, 28(sp) lw s7, 24(sp) lw s6, 20(sp) lw s5, 16(sp) lw s4, 12(sp) lw s3, 8(sp) lw s2, 4(sp) lw ra, 0(sp) addi sp, sp, 32 ret #============================================================ # mul_u16:無號 16-bit 乘法(RV32I:移位+加法) #============================================================ mul_u16: li t0, 0 mul16_loop: beq a1, x0, mul16_done andi t1, a1, 1 beq t1, x0, mul16_skip_add add t0, t0, a0 mul16_skip_add: slli a0, a0, 1 srli a1, a1, 1 j mul16_loop mul16_done: add a0, x0, t0 ret ``` </details> ::: --- ## 4. Using s* Registers * **Calling convention:** `s0–s11` are **callee-saved**. Using them inside a subroutine requires push/pop (`sw/lw`). For **leaf functions** (no further `jal` calls), switching to `a*/t*` **eliminates stack traffic**. * **V1 strategy in this project:** * `main`: keeps `s0` as a persistent suite flag (stable across multiple `jal`). * **`add_bf16`**: previously relied heavily on `s*`; now **converted entirely to `a*/t*`**, true leaf, no `sw/lw`. * `sub_bf16`, `f32_to_bf16`, `bf16_to_f32`: already leaf; continue using `a*/t*`. * `mul_bf16`, `div_bf16`, `sqrt_bf16`: **retain V0 stack-based design** due to algorithm complexity and high register pressure, as well as consistency with reference C behavior. (Future versions could leafify these for performance, but current focus is optimizing the frequently-called `add_bf16`.) * **Trade-off:** optimizing the most frequently executed and branch-dense function (`add_bf16`) first provides the greatest benefit; others remain unchanged for stability and validation clarity. --- ## Performance Comparison (Ripes, with print I/O) | Metric | **V0** | **V1** | **Δ (V1 − V0)** | | :----------------- | -----: | --------: | -----------------: | | **Cycles** | 1572 | **1531** | **−41 (−2.6%)** | | **Instr. retired** | 1105 | **1093** | **−12 (−1.1%)** | | **CPI** | 1.42 | **1.40** | **−0.02 (−1.4%)** | | **IPC** | 0.703 | **0.714** | **+0.011 (+1.6%)** | :::success V0 ![image](https://hackmd.io/_uploads/HkLbJscpxe.png) V1 ![image](https://hackmd.io/_uploads/ByZk1ocTxg.png) ::: **Interpretation** * Removing 12 redundant instructions in `main` directly reduces retired count. * Leafification and segmented normalization in `add_bf16` eliminate branches and memory traffic, yielding fewer cycles. * CPI/IPC slightly improved. > Note: results include `ecall` I/O overhead. > Under microbenchmark mode (no printing, using CSR `cycle/instret`), computational gains are even more pronounced. --- ## 5. Code Excerpts ### `main` (removed post-call clearing) ```asm - jal ra, add_bf16 - slli a0, a0, 16 - srli a0, a0, 16 + jal ra, add_bf16 ``` ### `add_bf16` (normalization: while → one-step segmented shift) ```asm - add_norm_loop: - andi t6, s5, 0x80 - bnez t6, add_pack - addi s3, s3, -1 - bge x0, s3, add_ret_zero - slli s5, s5, 1 - j add_norm_loop + # Check leading bits and decide direct left shift (1–7 bits) + andi t6, t5, 0x40 ; bnez t6, add_shift_1 + andi t6, t5, 0x20 ; bnez t6, add_shift_2 + ... + slli t5, t5, 7 + addi a6, a6, -7 ``` --- ## 6. Correctness (Identical to V0) * **NaN propagation, ±Inf, ±0:** follow reference C rules. * **Subnormals flushed to zero.** * **`mul_bf16`:** RNE rounding via guard/sticky bits. * **`add_bf16`:** behaves as defined in Quiz 1 Problem C after normalization. * **`sqrt_bf16`:** binary search with even/odd exponent adjustment; within allowed error range. :::success The test result ![image](https://hackmd.io/_uploads/BkrPJic6le.png) :::