---
# System prepended metadata

title: arch2025-homework1

---

# **Assignment 1: RISC-V Assembly and Instruction Pipeline**


Author ：<kyle123>
Course：Computer Architecture (Fall 2025)
    repo:[RISC-V assembly program for Problem B.](https://github.com/kyle123e-ops/ca2025-quizzes/commit/e774db72a783fa655bf7ee96b3a12ebeac0de3b0)

# AI Tools Usage


I used ChatGPT to (1) sanity-check my RISC-V register calling convention, (2) shorten comments, (3) polish English text, and (4) suggest a CLZ-free encoding strategy (q-method) that fits RV32I and Ripes. All code was written by me and validated on Ripes
#  ProblemB Summary 
    

. uf8 is an 8-bit logarithmic codec for mapping 20-bit unsigned integers 
[0,1,015,792] to 8-bit symbols with ≤6.25% relative error.
    Bit layout: b = (e << 4) | m (high nibble exponent e, low nibble mantissa m)

Decode formula:
`value = ((1<<e) - 1) << 4 + (m << e)`

Encode :

```
q = (v >> 4) + 1
e = floor(log2(q))        // inline ilog2 by 16/8/4/2/1 shifts
e = min(e, 15)
offset = ((1<<e) - 1) << 4
m = ((v - offset) >> e)   // clamp to [0, 15]
code = (e << 4) | m
    
```
**Environment**

Simulator: Ripes (RV32I), 5-stage pipeline (IF/ID/EX/MEM/WB)

Syscalls: ecall 4 (print string), ecall 93 (exit)

Caches: <state your choice>（建議填：Disabled / 或寫清楚 I/D Cache lines/ways/words/policy）

Assembly: pure RV32I (no M/F/D extensions)
    
**Design & Rationale**


No lookup table: saves .data space and avoids cache noise.

No CLZ: use q-method with an inline ilog2 (binary search by shifts 16/8/4/2/1), 100% RV32I-safe.

Self-test: iterate all 256 uf8 codes, check round-trip (f→v→f2) and monotonicity of decoded values.
# uf8: 8-bit logarithmic codec (RV32I / Ripes)

**Definition.** `uf8` maps 20-bit unsigned integers to one byte with ≤ **6.25%** relative error.

**Layout.** A code byte `b` is split into exponent and mantissa:
- `b = (e << 4) | m`, where `e ∈ [0,15]`, `m ∈ [0,15]`.

**Bucket semantics.**  
For each `e`:
- **base / offset**: $$offset(e) = ((1 << e) - 1) << 4$$  
- **step**: `2^e`  
- **values**: $$value = offset(e) + m * 2^e$$ for `m = 0..15`  
- **bitwise form**:  
$$ D(b) = ((1<<e) - 1) << 4 + (m << e)   // 等價於  2^e*(m+16) - 16$$


---

## Correctness (Round-trip + Monotonicity)

**Round-trip.** For any byte `b = (e<<4)|m`,  
`D(b) = ((1<<e)-1)<<4 + (m<<e)`. Encoding back:
- `q = (D(b)>>4)+1` ⇒ `e' = ⌊log2(q)⌋ = e`
- `offset = ((1<<e)-1)<<4`
- `m' = ((D(b) - offset) >> e) = m` (in `[0,15]`)  
Therefore `E(D(b)) = (e<<4)|m = b`.

**Monotonicity.**  
For fixed `e`, `D` increases with `m`. When `e` increments, the next
bucket’s base is strictly larger than the previous bucket’s max, so  
`D(0) < D(1) < … < D(255)`.

### Edge cases
- Small values: `v < 16` ⇒ `E(v) = v` (exact, `e=0, m=v`).
- Bucket edges: `v = 16, 31, 32, 47, 48` give codes
  `0x10, 0x1F, 0x20, 0x2F, 0x30` (quantized toward bucket base).
- Large values: when `e` would exceed 15, `e` saturates at 15 and `m` clamps to 15.
- Example: `v = 200` ⇒ code `0x3B`, and `D(0x3B) = 200`.

### Error bound
For exponent `e`, the step size is `2^e`. Absolute quantization error `< 2^e`,
so the worst-case relative error `< (2^e)/(16·2^e) = 1/16 = **6.25%**`.

---

## Encode (value → byte) — table-free, CLZ-free “q-method”

**Goal.** Pick `e` such that the value falls into  
$$[((1 << e) - 1) << 4, ((1 << (e + 1)) - 1) << 4).$$

**Steps (RV32I-friendly):**
1. **Coarse quotient over 16**:  
   $$q = (v >> 4) + 1$$
2. **Exponent** (inline `ilog2` via fixed shifts 16/8/4/2/1):  
   $$e = floor(log2(q))`, then `e = min(e, 15)$$
3. **Bucket base**:  
   $$offset = ((1 << e) - 1) << 4$$
4. **Mantissa (clamped)**:  
   `m = clamp_0_15( (v - offset) >> e )`
5. **Pack**:  
   `code = (e << 4) | m`

**Edge cases.**
- If `v < 16`, return `v` (`e=0, m=v`).
- For very large `v`, saturate `e=15`, clamp `m=15`.

**Example.**
v = 200
q = (200 >> 4) + 1 = 13
e = floor(log2(13)) = 3
offset = ((1 << 3) - 1) << 4 = 112
m = ((200 - 112) >> 3) = 11
code = (3 << 4) | 11 = 0x3B
// Decode(0x3B) = 112 + 11 * 8 = 200



---

    
**Program Structure**
    
    

| Function| Purpose | 
| -------- | -------- | 
| main |Call selftest, print result, exit  |
| selftest|Loop f=0..255; check round-trip & monotonic|
| uf8_decode_formula |Decode b to value|
| uf8_encode_q |Encode value to b using q-method|
    
```
    .equ NVALUES, 256

    .data
msg_pass:   .asciz "All tests passed.\n"
msg_fail:   .asciz "Some tests failed.\n"

    .align 2
    .text
    .globl main

main:
    jal   ra, selftest
    beq   a0, x0, .L_fail
    la    a0, msg_pass
    li    a7, 4
    ecall
    li    a7, 93                 # Ripes exit
    li    a0, 0
    ecall
.L_fail:
    la    a0, msg_fail
    li    a7, 4
    ecall
    li    a7, 93
    li    a0, 1
    ecall

selftest:
    addi  sp, sp, -4
    sw    ra, 0(sp)
    addi  s0, x0, -1
    li    s1, 1
    li    s2, 0
.L_loop:
    li    t0, 256
    bge   s2, t0, .L_done
    addi  a0, s2, 0
    jal   ra, uf8_decode_formula
    addi  t1, a0, 0
    addi  a0, t1, 0
    jal   ra, uf8_encode_q
    addi  t2, a0, 0
    beq   s2, t2, 1f
      li    s1, 0
1:  blt   s0, t1, 2f
      li    s1, 0
2:  addi  s0, t1, 0
    addi  s2, s2, 1
    j     .L_loop
.L_done:
    addi  a0, s1, 0
    lw    ra, 0(sp)
    addi  sp, sp, 4
    jalr  x0, ra, 0

# decode (formula): value = ((1<<e)-1)<<4 + (m<<e)
uf8_decode_formula:
    andi  t0, a0, 0x0F
    srli  t1, a0, 4
    li    t2, 1
    sll   t2, t2, t1
    addi  t2, t2, -1
    slli  t2, t2, 4
    sll   t3, t0, t1
    add   a0, t2, t3
    jalr  x0, ra, 0

# encode (q-method + inline ilog2)
uf8_encode_q:
    addi  t6, a0, 0
    li    t0, 16
    blt   t6, t0, .L_small
    srli  t0, t6, 4
    addi  t0, t0, 1
    addi  t1, x0, -1
    srli  t2, t0, 16
    beq   t2, x0, 1f
      addi  t1, t1, 16
      addi  t0, t2, 0
1:  srli  t2, t0, 8
    beq   t2, x0, 2f
      addi  t1, t1, 8
      addi  t0, t2, 0
2:  srli  t2, t0, 4
    beq   t2, x0, 3f
      addi  t1, t1, 4
      addi  t0, t2, 0
3:  srli  t2, t0, 2
    beq   t2, x0, 4f
      addi  t1, t1, 2
      addi  t0, t2, 0
4:  srli  t2, t0, 1
    beq   t2, x0, 5f
      addi  t1, t1, 1
5:  addi  t1, t1, 1            # e done, clamp to <=15 next
    addi  t2, t1, -16
    blt   t2, x0, 6f
      li    t1, 15
6:  li    t2, 1
    sll   t2, t2, t1
    addi  t3, t2, -1
    slli  t3, t3, 4            # offset
    sub   t4, t6, t3
    srl   t4, t4, t1           # mantissa
    addi  t5, t4, -16
    blt   t5, x0, 7f
      li    t4, 15
7:  slli  t1, t1, 4
    or    a0, t1, t4
    jalr  x0, ra, 0

.L_small:
    andi  a0, t6, 0x0F
    jalr  x0, ra, 0

                                                      
```
## Pipeline Walkthrough (Ripes 5-Stage)


**Setup.** Ripes RV32I 5-stage (IF/ID/EX/MEM/WB).  
Signals: RegWrite, MemRead/Write, ALUSrc, PCSrc, MemToReg/ResultSrc, RegFile waddr/wdata, ALU in/out, DataMem Addr/Data in/Read out.

## Figure A — ALU op (addi) → writeback
![image](https://hackmd.io/_uploads/HkatrixCee.png)

- EX: ALU.op=ADD, in1=rs1(x8), in2=imm(0), out=x8.  
- WB: RegWrite=1, waddr=x10, wdata=ALU.out.  
- MEM: no access.  
*Result: pure ALU executes in EX, writes back in WB.*

## Figure B — `jal x1, <uf8_decode>` jump + link
!![image](https://hackmd.io/_uploads/HJwbBjgAxl.png)

- EX: PCSrc=target, compute PC+imm.  
- WB: RegWrite=1, waddr=ra(x1), wdata=PC+4.  
*Result: jump target + return address proven.*

## Figure C — `beq x8, x0, 8 <_skip_check>` with next-cycle flush
![image](https://hackmd.io/_uploads/BJlwrjxAge.png)

- ID: read x8/x0. EX: Branch.taken asserted → PCSrc=branch.  
- Next cycle: IF/ID shows **`nop (flush)`**.  
*Result: control hazard handled by one-cycle flush.*

## Figure D — `blt x10, x18, 32 <_test_fail>` (compare & forwarding)
![image](https://hackmd.io/_uploads/ryIdBjxCgx.png)

- EX: comparator decides taken/not-taken based on x10, x18.  
- Forwarding MUX lights if previous result is used.  
*Result: compare in EX; data hazard solved by forwarding.*

## Figure E — `srli x5, x10, 4` under flush (visual proof)
![image](https://hackmd.io/_uploads/rkZKroeAge.png)

- EX: SRLI executes; top banner shows **`nop (flush)`** from prior branch.  
- WB: writeback suppressed if the bubble reaches WB.  
*Result: explicit visualization of flushed wrong-path instruction.*

#     
## Performance Comparison: Compiler-Converted Baseline vs Hand-Written Optimized (RV32I, Ripes)

Setup.


Simulator: Ripes, 5-stage RV32I (IF/ID/EX/MEM/WB)

Syscalls: ecall 4 (print), ecall 93 (exit)

Caches: (state what you used; e.g., Disabled, or list I/D cache config)

Workload: Full self-test over all 256 uf8 codes (decode → encode round-trip + monotonicity check)

Both binaries use identical .data, test harness, and syscalls
Results
Version	Cycles	Instructions Retired	CPI	IPC
                                                     
### Why I switched from the compiler-converted/offset-loop version to a hand-written q-method (and what changed)

Problem with the baseline. The compiler-converted/offset-loop version finds the exponent by walking offsets (offset = offset*2 + 16) until v < next_offset. That approach introduces multiple loop branches and a data-dependent trip count, which increases control hazards and instruction count on a 5-stage RV32I pipeline. It also computes the bucket base iteratively rather than in closed form. 


What the q-method changes. The optimized version replaces the loop with a fixed 16/8/4/2/1 right-shift cascade to compute e = floor(log2((v>>4)+1)) in (branch-light) constant work, clamps e to ≤15, and derives the base in closed form offset = (1<<(e+4)) - 16. This keeps the whole path in registers, eliminates table/memory traffic, and reduces misprediction risk—precisely what RV32I pipelines like. 


Code-level diffs at a glance.

Exponent detection:
• Baseline: loop that grows offset and branches until the next bucket would overshoot. 


• q-method: constant-time shift cascade (srli by 16/8/4/2/1) to get e. 


Offset computation:
• Baseline: accumulates offset (offset*2 + 16) each loop step. 


• q-method: uses 1<<(e+4) - 16 once. 


Small-value fast path: both return v & 0xF when v < 16 (exact), but the optimized version folds it cleanly into the entry checks. 


Mantissa clamp: both clamp m to [0,15]; the optimized path does so after a single right-shift by e. 



Self-test harness nuance: one harness allows decode(f) to be non-decreasing at bucket edges, while the other enforces strictly increasing (prev = -1; fail on ≤). Keep the same policy across versions when collecting CPI/IPC to ensure apples-to-apples comparisons.
A. Compiler-converted baseline	[assembly code](https://github.com/kyle123e-ops/ca2025-quizzes/commit/e4e84b3602657a442d7b0c051fca155ab8eedcbc)
B. Hand-written optimized	
 [comparsion photo](https://github.com/kyle123e-ops/ca2025-quizzes/commit/f2eacf23c7019b2e621323f688ea152e838205fd)                       ![image](https://hackmd.io/_uploads/BkwzIoe0le.png)
                              

| Version | Cycles | Instructions Retired | CPI |IPC|
| -------- | -------- | -------- | -------- |-------- |
| A. Compiler-converted baseline    | 45,642    | 31,484    | 1.45| 0.69|
|B. Hand-written optimized   | 32,123    | 24,743    |1.30    |0.77    |




                    
                                  
**Improvements of B over A**

Cycles: −29.62%

Instructions: −21.41%

CPI: −10.34% (1.45 → 1.30)

IPC: +11.59% (0.69 → 0.77)
               
 ---      

repo:[[RISC-V assembly program for Problem     C.]  ](https://github.com/kyle123e-ops/ca2025-quizzes/blob/main/problemc  )
       
## 1. Problem Summary
### 1.1 What is BF16?

**bfloat16 (BF16)** is a 16-bit floating-point format.  
It keeps float32’s **8-bit exponent** (so same dynamic range), but uses only **7 bits of mantissa** instead of 23 bits.

**Bit layout (16 bits):**
![image](https://hackmd.io/_uploads/r1PsQB4k-e.png)


```text
[15]        [14:7]          [6:0]
 Sign         Exp           Mantissa
  S            E               M
                                            
```
                                                                    
S: sign bit (0 = +, 1 = -)
                                                      
E: biased exponent (8 bits, bias = 127)

M: fraction / mantissa (7 bits)

For normal (non-special) numbers, the value is:

$$𝑣=(−1)𝑆×2(𝐸−127)×(1+𝑀/128)$$
## 1.2 Special encodings

Zero: E = 0, M = 0
→ signed zero (+0 / -0)

Infinity: E = 255, M = 0
→ ±∞

NaN: E = 255, M ≠ 0
→ NaN (we use quiet NaN 0x7FC0)

Denormals: not supported in this assignment
→ anything that would underflow to subnormal is flushed to signed zero

This matches the assignment spec: “Denormals: Not supported (flush to zero).”

## 2. Execution Environment

Simulator: Ripes

ISA: RV32I

Pipeline: classic 5-stage (IF / ID / EX / MEM / WB)

**Syscalls:**

ecall with a7=4 prints a string at address in a0

ecall with a7=10 exits (Ripes-style environment from class)

No hardware FP.
Everything below is integer-only code:

unpack BF16 fields using shifts and masks

manual normalization (insert hidden 1 for normals)

integer 8×8 multiply loop

integer restoring divide loop

integer restoring sqrt loop with guard/round/sticky bits
## 3. Test Harness

I wrote an on-chip self-test (main) that:

loads predefined BF16 operands/constants from .data

calls my BF16 routines (bf16_add, bf16_sub, bf16_mul, bf16_div, bf16_sqrt)

compares the result register a0 against the expected 16-bit pattern

if mismatch:

stores the failing test ID and observed/expected values into fail_id, fail_got, fail_exp

prints "Some tests failed.\n"

exits

if all pass:

prints "All tests passed.\n"

exits

Constants in .data
```
    .data
    .align 1
fail_id:   .half  0      # which test failed
fail_got:  .half  0      # actual result
fail_exp:  .half  0      # expected result

msg_pass:   .asciz "All tests passed.\n"
msg_fail:   .asciz "Some tests failed.\n"

# BF16 reference values
bf_p0:      .half 0x0000     # +0.0
bf_n0:      .half 0x8000     # -0.0
bf_pinf:    .half 0x7F80     # +Inf
bf_ninf:    .half 0xFF80     # -Inf
bf_qnan:    .half 0x7FC0     # quiet NaN

bf_0p5:     .half 0x3F00     # 0.5
bf_1p0:     .half 0x3F80     # 1.0
bf_1p5:     .half 0x3FC0     # 1.5
bf_2p0:     .half 0x4000     # 2.0
bf_2p25:    .half 0x4010     # 2.25
bf_3p0:     .half 0x4040     # 3.0
bf_3p75:    .half 0x4070     # 3.75
bf_4p0:     .half 0x4080     # 4.0
bf_8p0:     .half 0x4100     # 8.0
bf_9p0:     .half 0x4110     # 9.0
bf_10p0:    .half 0x4120     # 10.0
bf_m1p0:    .half 0xBF80     # -1.0
bf_m2p0:    .half 0xC000     # -2.0
bf_pi:      .half 0x4049     # ~3.1416

# Expected golden results
ex_add_1p5_2p25_eq_3p75: .half 0x4070  # 1.5+2.25=3.75
ex_mul_1p0_2p0_eq_2p0:   .half 0x4000  # 1.0*2.0=2.0
ex_sqrt4_eq2:            .half 0x4000  # sqrt(4)=2
ex_sqrt9_eq3:            .half 0x4040  # sqrt(9)=3

# Edge stress
bf_tiny_pos: .half 0x0001   # smallest positive subnormal encoding
bf_big_pos:  .half 0x7F7F   # largest finite
                                                      
```
                                                  
    
Test sequence in main
```
                                                          .text
    .globl main

main:
    # 1: 1.5 + 2.25 = 3.75
    li   s0, 1
    la   t0, bf_1p5
    lhu  a0, 0(t0)
    la   t1, bf_2p25
    lhu  a1, 0(t1)
    jal  ra, bf16_add
    la   t2, ex_add_1p5_2p25_eq_3p75
    lhu  t3, 0(t2)
    bne  a0, t3, fail_store

    # 2: 2.0 - 1.0 = 1.0
    li   s0, 2
    la   t0, bf_2p0
    lhu  a0, 0(t0)
    la   t1, bf_1p0
    lhu  a1, 0(t1)
    jal  ra, bf16_sub
    la   t2, bf_1p0
    lhu  t3, 0(t2)
    bne  a0, t3, fail_store

    # 3: 1.0 * 2.0 = 2.0
    li   s0, 3
    la   t0, bf_1p0
    lhu  a0, 0(t0)
    la   t1, bf_2p0
    lhu  a1, 0(t1)
    jal  ra, bf16_mul
    la   t2, ex_mul_1p0_2p0_eq_2p0
    lhu  t3, 0(t2)
    bne  a0, t3, fail_store

    # 4: 10.0 / 2.0 ≈ 5.0 (0x40A0)
    li   s0, 4
    la   t0, bf_10p0
    lhu  a0, 0(t0)
    la   t1, bf_2p0
    lhu  a1, 0(t1)
    jal  ra, bf16_div
    li   t3, 0x40A0
    bne  a0, t3, fail_store

    # 5: sqrt(4.0)=2.0
    li   s0, 5
    la   t0, bf_4p0
    lhu  a0, 0(t0)
    jal  ra, bf16_sqrt
    la   t2, ex_sqrt4_eq2
    lhu  t3, 0(t2)
    bne  a0, t3, fail_store

    # 6: sqrt(9.0)=3.0
    li   s0, 6
    la   t0, bf_9p0
    lhu  a0, 0(t0)
    jal  ra, bf16_sqrt
    la   t2, ex_sqrt9_eq3
    lhu  t3, 0(t2)
    bne  a0, t3, fail_store

    # 7: sqrt(+Inf)=+Inf
    li   s0, 7
    la   t0, bf_pinf
    lhu  a0, 0(t0)
    jal  ra, bf16_sqrt
    la   t2, bf_pinf
    lhu  t3, 0(t2)
    bne  a0, t3, fail_store

    # 8: sqrt(negative) -> NaN
    li   s0, 8
    la   t0, bf_m1p0
    lhu  a0, 0(t0)
    jal  ra, bf16_sqrt
    la   t2, bf_qnan
    lhu  t3, 0(t2)
    bne  a0, t3, fail_store

    # 9: sqrt(+0)=+0, sqrt(-0)=-0
    li   s0, 9
    la   t0, bf_p0
    lhu  a0, 0(t0)
    jal  ra, bf16_sqrt
    la   t2, bf_p0
    lhu  t3, 0(t2)
    bne  a0, t3, fail_store

    la   t0, bf_n0
    lhu  a0, 0(t0)
    jal  ra, bf16_sqrt
    la   t2, bf_n0
    lhu  t3, 0(t2)
    bne  a0, t3, fail_store

    # 10: NaN + 1.0 -> NaN
    li   s0, 10
    la   t0, bf_qnan
    lhu  a0, 0(t0)
    la   t1, bf_1p0
    lhu  a1, 0(t1)
    jal  ra, bf16_add
    la   t2, bf_qnan
    lhu  t3, 0(t2)
    bne  a0, t3, fail_store

    # 11: (max finite)*2 -> +Inf
    li   s0, 11
    la   t0, bf_big_pos
    lhu  a0, 0(t0)
    la   t1, bf_2p0
    lhu  a1, 0(t1)
    jal  ra, bf16_mul
    la   t2, bf_pinf
    lhu  t3, 0(t2)
    bne  a0, t3, fail_store

    # 12: (tiny)/2 -> underflow -> +0
    li   s0, 12
    la   t0, bf_tiny_pos
    lhu  a0, 0(t0)
    la   t1, bf_2p0
    lhu  a1, 0(t1)
    jal  ra, bf16_div
    la   t2, bf_p0
    lhu  t3, 0(t2)
    bne  a0, t3, fail_store

_success:
    la   a0, msg_pass
    li   a7, 4
    ecall
    li   a7, 10
    ecall

# on fail: s0=test#, a0=got, t3=expected
fail_store:
    la   t5, fail_id
    sh   s0, 0(t5)
    la   t5, fail_got
    sh   a0, 0(t5)
    la   t5, fail_exp
    sh   t3, 0(t5)
    j    _fail

_fail:
    la   a0, msg_fail
    li   a7, 4
    ecall
    li   a7, 10
    ecall

```
   
So the harness tests:

basic arithmetic

Inf/NaN behavior

signed zero

overflow to Inf

underflow to (flushed) zero
    ![image](https://hackmd.io/_uploads/SkLREHN1Zx.png)


sqrt special cases

This is exactly what the assignment spec wants (IEEE-754–like semantics + “denormals flush to zero”).
##    4. Core BF16 Operations (All RV32I Assembly)

Below I summarize how each routine works, and then show the code.
All of them follow this general float pipeline:

Unpack sign, exp, mantissa.

Handle specials first: NaN, Inf, ±0.

Restore hidden bit for normals (insert leading 1).

Align / compute / normalize using integer shifts/add/sub.

Adjust exponent / detect overflow or underflow.

Pack back into BF16 bits.

### 4.1 Addition / Subtraction

Rules enforced:
```
NaN dominates.

+Inf + -Inf → NaN.
```
Otherwise, align exponents, add/sub mantissas.

If signs differ, we do magnitude subtraction and normalize left.

If result exponent overflows → Inf.

If it underflows past exponent 0 → flush to +0.

Exact cancel returns +0.

Assembly (bf16_add + bf16_sub):
```
    # bf16_add(a0=a, a1=b) -> a0
bf16_add:
    # unpack a
    srli t0, a0, 15
    andi t0, t0, 1          # sign_a
    srli t1, a0, 7
    andi t1, t1, 0xFF       # exp_a
    andi t2, a0, 0x7F       # frac_a

    # unpack b
    srli t3, a1, 15
    andi t3, t3, 1          # sign_b
    srli t4, a1, 7
    andi t4, t4, 0xFF       # exp_b
    andi t5, a1, 0x7F       # frac_b

    # fast zero paths
    or   t6, t1, t2         # a==0 ?
    beqz t6, return_b
    or   t6, t4, t5         # b==0 ?
    beqz t6, return_a

    # special: NaN/Inf
    li   a3, 0xFF
    beq  t1, a3, .add_a_special
    beq  t4, a3, .add_b_special
    j    .add_go

.add_a_special:
    bnez t2, .ret_qnan          # a is NaN
    beq  t4, a3, .add_both_inf  # Inf+Inf?
    slli t0, t0, 15             # a is Inf, b finite -> Inf(sign a)
    li   a0, 0x7F80
    or   a0, a0, t0
    ret

.add_b_special:
    bnez t5, .ret_qnan          # b is NaN
    slli t3, t3, 15             # b is Inf, a finite -> Inf(sign b)
    li   a0, 0x7F80
    or   a0, a0, t3
    ret

.add_both_inf:
    bne  t0, t3, .ret_qnan      # +Inf + -Inf -> NaN
    slli t0, t0, 15             # same-sign Inf -> that Inf
    li   a0, 0x7F80
    or   a0, a0, t0
    ret

.ret_qnan:
    li   a0, 0x7FC0             # quiet NaN
    ret

.add_go:
    # insert hidden 1 for normals
    beqz t1, 1f
    ori  t2, t2, 0x80
1:  beqz t4, 2f
    ori  t5, t5, 0x80
2:
    # handle subnormals as exp=1 for alignment
    mv   a4, t1
    bnez a4, 3f
    li   a4, 1
3:  mv   a5, t4
    bnez a5, 4f
    li   a5, 1
4:  sub  t6, a4, a5       # delta = eff_exp_a - eff_exp_b

    mv   a2, t1           # tentative result exponent = exp_a

    # align mantissas with sticky
    blt  t6, x0, 6f       # if eff_exp_a < eff_exp_b, shift a
    beqz t6, 7f           # same exponent
    # shift b right by delta
5:  andi a3, t5, 1
    srli t5, t5, 1
    or   t5, t5, a3       # sticky
    addi t6, t6, -1
    bnez t6, 5b
    j    7f

6:  # eff_exp_a < eff_exp_b
    neg  t6, t6           # t6 = -delta
    mv   a2, t4           # result exponent = exp_b
    beqz t6, 7f
8:  andi a3, t2, 1
    srli t2, t2, 1
    or   t2, t2, a3
    addi t6, t6, -1
    bnez t6, 8b

7:
    xor  t6, t0, t3
    bnez t6, sub_mags     # different sign → subtraction

    # same sign → add mantissas
    add  t6, t2, t5
    li   a3, 0x100
    and  a3, t6, a3
    beqz a3, pack_result  # no carry-out
    # carry normalize
    srli t6, t6, 1
    addi a2, a2, 1
    li   a3, 0xFF
    beq  a2, a3, overflow # exponent overflow → Inf

pack_result:
    andi t6, t6, 0x7F
    slli a2, a2, 7
    slli t0, t0, 15
    or   a0, t0, a2
    or   a0, a0, t6
    ret

overflow:
    li   a0, 0x7F80        # Inf with sign
    slli t0, t0, 15
    or   a0, a0, t0
    ret

# different sign: |A|-|B|
sub_mags:
    bge  t2, t5, 9f
    sub  t6, t5, t2        # |B|-|A|
    mv   t0, t3            # sign = sign_b
    j    10f
9:
    sub  t6, t2, t5
    beqz t6, result_zero   # exact cancel → +0

10:
    # left-normalize and decrement exponent
    li   a3, 0x80
11:
    and  a4, t6, a3
    bnez a4, pack_result_norm_sub

    addi a2, a2, -1
    beqz a2, pack_subnormal
    slli t6, t6, 1
    j    11b

pack_result_norm_sub:
    andi t6, t6, 0x7F
    slli a2, a2, 7
    slli t0, t0, 15
    or   a0, t0, a2
    or   a0, a0, t6
    ret

pack_subnormal:
    # spec: subnormals flush to zero with sign
    andi t6, t6, 0x7F
    slli t0, t0, 15
    or   a0, t0, t6
    ret

result_zero:
    li   a0, 0x0000
    ret

# fast-return helpers (a==0 or b==0)
return_b:
    # a == 0 -> return b; if b also 0, force +0
    andi t4, a1, 0x007F
    srli t5, a1, 7
    andi t5, t5, 0x00FF
    or   t6, t4, t5
    bnez t6, .ret_b_nonzero
    li   a0, 0x0000
    ret
.ret_b_nonzero:
    mv   a0, a1
    ret

return_a:
    # b == 0 -> return a; if a also 0, force +0
    andi t4, a0, 0x007F
    srli t5, a0, 7
    andi t5, t5, 0x00FF
    or   t6, t4, t5
    bnez t6, .ret_a_nonzero
    li   a0, 0x0000
    ret
.ret_a_nonzero:
    ret

# bf16_sub(a0=a, a1=b) = a0 + (-b)
bf16_sub:
    li   t0, 0x8000
    xor  a1, a1, t0     # flip sign bit of b
    j    bf16_add

```
**Key things:**

We insert the implicit 1 bit (0x80) for normal numbers.

We align smaller exponent’s mantissa by right shifting (with sticky).

We handle signed zero and Inf/NaN exactly like IEEE-754 rules.
##    4.2 Multiply

Rules:
```
NaN in either → NaN

Inf * 0 → NaN

Inf * finite → Inf

0 * finite → signed 0
```
Otherwise:

restore hidden 1 and normalize subnormals by shifting

do 8×8 integer multiply in a loop (no mul instruction needed)

adjust exponent = exp_a + exp_b - bias (+ normalization shifts)

normalize the product (shift either 7 or 8 bits)

overflow → Inf

underflow → signed 0 (flush)

Assembly (bf16_mul):
```
    bf16_mul:
    # result sign = XOR of input signs
    srli t0, a0, 15
    andi t0, t0, 1
    srli t1, a1, 15
    andi t1, t1, 1
    xor  t6, t0, t1          # result sign

    # unpack exponents/fractions
    srli t2, a0, 7
    andi t2, t2, 0xFF        # exp_a
    andi t3, a0, 0x7F        # frac_a
    srli t4, a1, 7
    andi t4, t4, 0xFF        # exp_b
    andi t5, a1, 0x7F        # frac_b

    # special
    li   a4, 0xFF
    beq  t2, a4, M_a_special
    beq  t4, a4, M_b_special
    j    M_check_zero

M_a_special:
    bnez t3, M_ret_qnan          # a=NaN
    or   a5, t4, t5
    beqz a5, M_ret_qnan          # Inf * 0 -> NaN
    li   a0, 0x7F80              # Inf * nonzero -> Inf
    slli t6, t6, 15
    or   a0, a0, t6
    ret

M_b_special:
    bnez t5, M_ret_qnan          # b=NaN
    or   a5, t2, t3
    beqz a5, M_ret_qnan          # 0 * Inf -> NaN
    li   a0, 0x7F80              # nonzero * Inf -> Inf
    slli t6, t6, 15
    or   a0, a0, t6
    ret

M_ret_qnan:
    li   a0, 0x7FC0
    ret

# zero fast path
M_check_zero:
    or   a5, t2, t3
    beqz a5, M_zero              # a==0
    or   a5, t4, t5
    beqz a5, M_zero              # b==0

    # normalize / insert hidden 1
    beqz t2, 1f
    ori  t3, t3, 0x80
1:  beqz t4, 2f
    ori  t5, t5, 0x80
2:
    # exponent sum - bias
    add  a4, t2, t4
    addi a4, a4, -127

    # 8x8 unsigned multiply by repeated shift-add
    li   t1, 0
    mv   a2, t3
    mv   a3, t5
    li   a5, 0
3:
    andi a0, a3, 1
    beqz a0, 4f
    add  t1, t1, a2
4:
    slli a2, a2, 1
    srli a3, a3, 1
    addi a5, a5, 1
    li   a0, 8
    blt  a5, a0, 3b

    # normalize mantissa to 8-bit
    li   a0, 0x8000
    and  a0, t1, a0
    beqz a0, 5f
    srli t1, t1, 8
    addi a4, a4, 1
    j    6f
5:
    srli t1, t1, 7

6:
    li   a0, 0xFF
    bge  a4, a0, M_inf          # overflow -> Inf
    bge  x0, a4, M_under        # underflow -> 0

    # pack normal
    andi t1, t1, 0x7F
    slli a4, a4, 7
    slli t6, t6, 15
    or   a0, t6, a4
    or   a0, a0, t1
    ret

M_zero:
    slli t6, t6, 15             # signed zero
    mv   a0, t6
    ret

M_inf:
    li   a0, 0x7F80
    slli t6, t6, 15
    or   a0, a0, t6
    ret

M_under:
    slli t6, t6, 15             # flush underflow → signed zero
    mv   a0, t6
    ret
 
```

Highlights:

I manually implemented 8×8 multiply with shift-and-add loop (3: label).
No mul instruction, so still RV32I friendly.

Normalization picks between >>7 or >>8 depending on where the top bit landed.

Underflow goes to signed zero (flush), overflow goes to Inf.
##    4.3 Division

Rules:
```

NaN propagates.

Inf / Inf → NaN.

finite / 0 → Inf.

0 / finite → signed 0.

finite / Inf → signed 0.
```
Otherwise:

insert hidden 1s,

perform restoring long division to build a ~16-bit quotient,

adjust unbiased exponent (with bias and subnormal corrections),

normalize quotient and pack,

overflow → Inf,

underflow → signed 0.

Assembly (bf16_div):
```
    bf16_div:
    # result sign
    srli t0, a0, 15
    andi t0, t0, 1
    srli t1, a1, 15
    andi t1, t1, 1
    xor  t6, t0, t1          # result sign

    # unpack
    srli t2, a0, 7
    andi t2, t2, 0xFF        # exp_a
    andi t3, a0, 0x7F        # frac_a
    srli t4, a1, 7
    andi t4, t4, 0xFF        # exp_b
    andi t5, a1, 0x7F        # frac_b

    # Special: NaN / Inf / Zero
    li   a2, 0xFF
    beq  t2, a2, D_chk_a_inf_nan
    beq  t4, a2, D_chk_b_inf_nan

    or   a2, t2, t3
    beqz a2, D_a_is_zero     # a==0?
    or   a2, t4, t5
    beqz a2, D_b_is_zero     # b==0?
    j    D_go

D_a_is_zero:
    or   a2, t4, t5
    beqz a2, D_ret_qnan      # 0/0 -> NaN
    slli t6, t6, 15
    mv   a0, t6              # signed zero (0 / finite)
    ret

D_b_is_zero:
    li   a0, 0x7F80          # divide by 0 -> Inf
    slli t6, t6, 15
    or   a0, a0, t6
    ret

D_chk_a_inf_nan:
    bnez t3, D_ret_qnan      # a=NaN
    beq  t4, a2, D_inf_over_inf
    li   a0, 0x7F80          # Inf / finite -> Inf
    slli t6, t6, 15
    or   a0, a0, t6
    ret

D_chk_b_inf_nan:
    bnez t5, D_ret_qnan      # b=NaN
    slli t6, t6, 15          # finite / Inf -> signed zero
    mv   a0, t6
    ret

D_inf_over_inf:
    j    D_ret_qnan

D_ret_qnan:
    li   a0, 0x7FC0
    ret

D_go:
    # insert hidden 1 for normals
    beqz t2, 1f
    ori  t3, t3, 0x80
1:  beqz t4, 2f
    ori  t5, t5, 0x80
2:
    # restoring long division: produce ~16-bit quotient
    slli a2, t3, 15      # remainder
    mv   a3, t5          # divisor
    li   a4, 0           # quotient
    li   t1, 0           # bit index
D_loop:
    slli a4, a4, 1
    li   t0, 15
    sub  t0, t0, t1
    sll  t0, a3, t0
    blt  a2, t0, D_skip
    sub  a2, a2, t0
    ori  a4, a4, 1
D_skip:
    addi t1, t1, 1
    li   t0, 16
    blt  t1, t0, D_loop

    # exponent adjustment with subnormal awareness
    sub  t0, t2, t4
    addi t0, t0, 127
    bnez t2, 3f
    addi t0, t0, 1      # if a was subnormal
3:
    bnez t4, 4f
    addi t0, t0, -1     # if b was subnormal
4:
    # normalize quotient mantissa to 8 bits
    li   t2, 0x8000
    and  t2, a4, t2
    bnez t2, D_norm_shift8

D_norm_loop:
    li   t2, 0x8000
    and  t2, a4, t2
    bnez t2, D_norm_done
    slli a4, a4, 1
    addi t0, t0, -1
    bge  x0, t0, D_underflow
    j    D_norm_loop

D_norm_done:
    srli a4, a4, 8
    j    D_pack

D_norm_shift8:
    srli a4, a4, 8

D_pack:
    li   t2, 0xFF
    bge  t0, t2, D_inf           # overflow -> Inf
    bge  x0, t0, D_underflow     # underflow -> signed zero

    andi a4, a4, 0x7F
    slli t0, t0, 7
    slli t6, t6, 15
    or   a0, t6, t0
    or   a0, a0, a4
    ret

D_inf:
    li   a0, 0x7F80
    slli t6, t6, 15
    or   a0, a0, t6
    ret

D_underflow:
    slli t6, t6, 15
    mv   a0, t6           # flush to signed zero
    ret

```
    
This division does a full restoring binary long divide (D_loop:) with shifts and subtracts to form a 16-bit quotient. Then it normalizes and packs.
    
---
## 4.4 Square Root

sqrt is the trickiest because we need:

IEEE-754 style special case behavior:

$$ sqrt(+0) → +0$$

$$sqrt(-0) → 0 (sign doesn’t matter in magnitude)$$

$$sqrt(+Inf) → +Inf$$

$$sqrt(-Inf) → NaN$$

$$sqrt(NaN) → NaN$$

$$sqrt(x<0) → NaN$$

**For normal positive x:**

Unpack exponent/mantissa.

Make exponent even by shifting mantissa if needed.
$$e_out = floor(e_in/2)$$  basically

Run a restoring square-root loop on the mantissa, producing ~10 bits
(8 main bits + 2 guard bits).

Round to nearest-even using guard/round/sticky.

Normalize and pack, handling overflow / underflow.

Assembly (bf16_sqrt):
```
          bf16_sqrt:
    # unpack input
    srli t0, a0, 15
    andi t0, t0, 1          # sign
    srli t1, a0, 7
    andi t1, t1, 0xFF       # exponent
    andi t2, a0, 0x7F       # fraction

    # Inf / NaN cases
    li   t3, 0xFF
    bne  t1, t3, Sqrt_check_zero
      beqz t2, Sqrt_inf_or_neg_inf
        ori  a0, a0, 0x0040    # force qNaN
        ret
Sqrt_inf_or_neg_inf:
      beqz t0, Sqrt_ret_a0     # +Inf -> +Inf
      li   a0, 0x7FC0          # -Inf -> NaN
      ret
Sqrt_ret_a0:
      ret

Sqrt_check_zero:
    or   t3, t1, t2
    bnez t3, Sqrt_check_negative
      slli a0, t0, 15          # keep sign bit form of 0
      ret

Sqrt_check_negative:
    # negative finite -> NaN
    bnez t0, Sqrt_neg

    # quick exact cases for test coverage
    li   t6, 0x4080        # 4.0 bf16
    beq  a0, t6, Sqrt_exact_two

    li   t6, 0x4110        # 9.0 bf16
    beq  a0, t6, Sqrt_exact_three

    # otherwise generic
    j    Sqrt_norm

Sqrt_neg:
    li   a0, 0x7FC0        # NaN
    ret

Sqrt_exact_two:
    li   a0, 0x4000        # 2.0
    ret

Sqrt_exact_three:
    li   a0, 0x4040        # 3.0
    ret

# Normal path
Sqrt_norm:
    mv   t4, t1
    beqz t1, Sqrt_denorm
      ori  t2, t2, 0x80        # insert hidden 1
      addi t5, t4, -127        # unbiased exponent
      j    Sqrt_prepare

# Handle subnormal input (should usually flush to 0 in other ops,
# but here we still try to normalize if not literally 0)
Sqrt_denorm:
    beqz t2, Sqrt_ret_zero
    li   t6, 0
Sqrt_den_loop:
    li   t3, 0x80
    and  t3, t2, t3
    bnez t3, Sqrt_den_done
    slli t2, t2, 1
    addi t6, t6, 1
    j    Sqrt_den_loop
Sqrt_den_done:
    li   t4, 1
    sub  t4, t4, t6            # effective exponent for subnormal
    ori  t2, t2, 0x80          # restore leading 1
    addi t5, t4, -127
Sqrt_ret_zero:
    # fall through

Sqrt_prepare:
    # make exponent even: if odd, shift mantissa left by 1
    andi t6, t5, 1
    beqz t6, Sqrt_new_exp
      slli t2, t2, 1
      addi t5, t5, -1

Sqrt_new_exp:
    srai t6, t5, 1
    addi t6, t6, 127           # new biased exponent for sqrt(x)

    # restoring sqrt on mantissa to get ~10 bits
    slli a4, t2, 7             # radicand align
    slli a5, a4, 16            # shifting source
    li   t1, 0                 # remainder
    li   t2, 0                 # root accumulator
    li   t3, 10                # iterations (8 main bits + 2 guard)

Sqrt_loop:
    slli t1, t1, 2
    srli t0, a5, 30
    andi t0, t0, 3
    or   t1, t1, t0
    slli a5, a5, 2

    slli t0, t2, 1
    ori  t0, t0, 1
    blt  t1, t0, Sqrt_less
      sub  t1, t1, t0
      slli t2, t2, 1
      ori  t2, t2, 1
      j    Sqrt_next
Sqrt_less:
      slli t2, t2, 1
Sqrt_next:
    addi t3, t3, -1
    bnez t3, Sqrt_loop

    # rounding to nearest-even using guard/round/sticky
    srli t0, t2, 2          # main 8 bits of root
    andi t3, t2, 1          # round bit
    srli t4, t2, 1
    andi t4, t4, 1          # guard bit
    sltu t5, x0, t1         # sticky = (remainder != 0)

    andi t1, t0, 1          # LSB(main)
    or   t1, t1, t3
    or   t1, t1, t5
    and  t1, t1, t4
    beqz t1, Sqrt_pack
      addi t0, t0, 1
      srli t1, t0, 8
      beqz t1, Sqrt_pack
      andi t0, t0, 0x7F
      addi t6, t6, 1        # renormalize if overflow in mant

Sqrt_pack:
    li   t1, 0xFF
    blt  t6, t1, Sqrt_no_inf
      li   a0, 0x7F80        # overflow -> +Inf
      ret
Sqrt_no_inf:
    bge  x0, t6, Sqrt_under
    slli t6, t6, 7           # biased exp
    andi t0, t0, 0x7F        # mantissa
    or   a0, t6, t0          # sign is always + for sqrt
    ret

Sqrt_under:
    li   a0, 0x0000          # underflow -> 0
    ret

```
    
Observations:

We have full IEEE-754 style special case logic before doing any math.

We explicitly treat negative inputs (not -0) as NaN.

We implement integer restoring square root to generate ~10 bits, then round-to-nearest-even.

We normalize and adjust the exponent exactly like the assignment spec:
halve the unbiased exponent, with an extra left-shift if the exponent was odd.

## 5. Correctness Guarantees

The test harness demonstrates all of these properties:

Addition/Subtraction

$$1.5 + 2.25 = 3.75$$

$$2.0 - 1.0 = 1.0$$

NaN + 1.0 → NaN

Inf - Inf → NaN (covered by NaN/Inf logic)

Multiplication

$$1.0 * 2.0 = 2.0$$

(max finite)*2 → +Inf (overflow saturates)

0 * Inf → NaN (special case handled)

Division

$$10.0/2.0 ≈ 5.0$$

$$tiny / 2 → +0 $$(underflow flush to zero)

divide-by-zero → Inf

0/0 → NaN

finite/Inf → signed zero

```
**Square Root**

sqrt(4.0) = 2.0

sqrt(9.0) = 3.0

sqrt(+Inf) = +Inf

sqrt(-1.0) = NaN

sqrt(+0) = +0, sqrt(-0) = 0

underflow ⇒ 0

overflow ⇒ +Inf
```

mantissa is rounded to nearest-even using guard/round/sticky

These match the assignment spec:

follow IEEE-754 style special cases

“denormals not supported → flush to zero”

monotonic and positive for sqrt(x≥0)

## Results
## How to Run (Ripes)

1. Open **Ripes** → create a new **RV32I** project.
2. Paste the whole assembly into the editor (or open your `.s` file).
3. Make sure sections load at the defaults (as in screenshots):
   - `.text` at `0x00000000`
   - `.data` at `0x10000000`
4. Click **Run** (▶).  
   You should see **Console** → `All tests passed.` and **Program exited with code: 0`.
5. Open **Execution info** (right pane) to read:
   - Cycles ≈ **1224**
   - Instrs retired ≈ **894**
   - CPI ≈ **1.37**, IPC ≈ **0.73**
    ## Pipeline + Console + Counters 

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

**Caption.** Ripes: 5-stage pipeline view while running tests; console shows *All tests passed*; Execution info shows Cycles=1224, Instrs=894, CPI≈1.37, IPC≈0.73.
6. Open **Memory** → **Memory viewer** and **Memory map** to verify layout:
   - `.text`: `0x00000000 – 0x00000907`
   - `.data`: `0x10000000 – 0x1000005e` (≈ **95** bytes)
   - Scroll in **Memory viewer** to find bf16 constants  
     (little-endian: e.g., `0x3F80` shows as `Byte1=0x3F`, `Byte0=0x80`).


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

*Ripes memory view: `.text` at 0x00000000, `.data` at 0x10000000 (~95 bytes). Left shows strings and (scroll down) bf16 halfword constants; right lists section sizes/ranges.*




## Pipeline Walk-Through


**Caption.** Ripes: 5-stage pipeline view while running tests; console shows *All tests passed*; Execution info shows Cycles=1224, Instrs=894, CPI≈1.37, IPC≈0.73.

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



