5.1.8 · D4Instruction Set Architecture (ISA)

Exercises — RISC-V extensions (M, A, F, D, V, C)

2,273 words10 min readBack to topic

These graded problems test the RISC-V extensions (M, A, F, D, V, C) material. Each level goes deeper: L1 you just recognise the pieces, by L5 you build something. Every problem has a full worked solution folded up — try it first, then unfold.

Before we start, one reminder of notation used everywhere below:

Prerequisites if any line feels unfamiliar: 5.1.07-RISC-V-base-ISA, 5.2.03-IEEE-754-floating-point, 3.3.06-Instruction-encoding, 6.4.02-Cache-coherence-protocols.


Level 1 — Recognition

Recall Solution L1.1

Read the shape of each mnemonic:

  • MULHU — a multiply → M extension.
  • sc.w — store-conditional, an atomic → A extension.
  • FADD.S — the .S suffix means single-precision → F extension.
  • FLD — load a double (.D family, the D in FLD) → D extension.
  • amoadd.w — atomic memory operation → A extension.
  • DIVU — unsigned divide → M extension.

The tell: MUL/DIV/REM ⇒ M. LR/SC/AMO ⇒ A. .S ⇒ F. .D ⇒ D.

Recall Solution L1.2
  • Sign = 1 bit, exponent = 8 bits, mantissa = 23 bits. Total ✓.
  • Bias .

Level 2 — Application

Recall Solution L2.1

0x00010000 . Product (a 64-bit value).

  • MUL takes the lower 32 bits [31:0] 0x00000000 .
  • MULHU takes the upper 32 bits [63:32] 0x00000001 .

This is why two instructions exist: the 32-bit result register cannot hold a 64-bit product, so the answer is split.

Recall Solution L2.2

Write the hex in binary, 4 bits per digit: 0100 0000 0100 1001 0000 1111 1101 1011.

  • Sign = bit 31 = 0 → positive, .
  • Exponent = next 8 bits 1000 0000 . Real exponent .
  • Mantissa = last 23 bits 100 1001 0000 1111 1101 1011. Its fractional value is
  • Value .

That is 0x40490FDB is the standard single-precision encoding of .


Level 3 — Analysis

Recall Solution L3.1

Same bits, two interpretations.

Signed (MULH): 0xFFFFFFFF = , 0x00000002 = . Product . In 64-bit two's complement . Upper 32 bits [63:32] = 0xFFFFFFFF (all sign bits).

Unsigned (MULHU): 0xFFFFFFFF = , 0x00000002 = . Product . Upper 32 bits = 0x00000001.

Why different: the bits multiplied are identical, but signed math sign-extends 0xFFFFFFFF into a small negative number, while unsigned math treats it as a huge positive number. The low 32 bits happen to match (0xFFFFFFFE), but the high halves diverge completely.

Recall Solution L3.2
  1. lr.w t0, (a0) loads the counter and places a reservation on that address for our core.
  2. The other core writes to the same cache line. The coherence protocol (see 6.4.02-Cache-coherence-protocols) sends an invalidation → our reservation is cleared.
  3. sc.w t2, t1, (a0) checks the reservation, finds it invalid, so it does not store and sets t2 = 1 (failure).
  4. bnez t2, retry sees t2 ≠ 0 and jumps back to retry, re-reading the now-updated value.

The guarantee: a store only lands if no one touched the address in between, so no increment is ever lost. Success ⇒ t2 = 0; failure ⇒ t2 = 1.


Level 4 — Synthesis

Recall Solution L4.1
retry:
    lr.w  t0, (a0)        # t0 = current value, reserve address
    bge   t0, a1, done    # if current >= a1, nothing to do
    sc.w  t2, a1, (a0)    # try to store a1
    bnez  t2, retry       # store failed? someone raced us -> retry
done:

Reasoning step by step:

  • lr.w reads and reserves.
  • bge t0, a1, done — if the existing value is already the larger one, we skip the store entirely (a store would only waste a reservation).
  • Otherwise sc.w attempts the write; failure ⇒ retry with the fresh value.

Single instruction equivalent: amomax.w t0, a1, (a0) — atomically reads mem[a0], writes max(old, a1), returns the old value in t0. It is one hardware-locked operation instead of a 4-instruction loop, so it is faster and cannot livelock.

Recall Solution L4.2
  1. Unpack (see 5.2.03-IEEE-754-floating-point):
    • → exp , mantissa
    • → exp , mantissa
  2. Align to the larger exponent (): shift 's mantissa right by places: .
  3. Add mantissas: → value .
  4. Normalize: already has one bit before the point — done.
  5. Pack: sign , exp , mantissa (the .11 after the implicit 1). Bits: 0 10000000 11000000000000000000000 = 0x40600000.

Check: decodes to ✓.


Level 5 — Mastery

Recall Solution L5.1

Single precision has a 23-bit mantissa plus 1 implicit bit = 24 significant bits.

  • . The next representable single-precision number above it is , not , because at this magnitude the spacing between representable values (the ULP) is .
  • Adding lands exactly halfway; round-to-nearest-even rounds down to .
  • Result: sum stays 16777216.0 — the +1 vanished.

Double precision: 52+1 = 53 significant bits. At magnitude the ULP is , far below 1, so is represented exactly. This is exactly why summing millions of values uses a double accumulator.

Recall Solution L5.2

a = 0x80000000 signed . b = 3 unsigned. True product .

In 64-bit two's complement: .

  • MUL = low 32 bits [31:0] = 0x80000000.
  • MULHSU = high 32 bits [63:32] = 0xFFFFFFFE (signed×unsigned high half).

Reconstruct: . Read 0xFFFFFFFE as signed : Exactly the true product. MULHSU is the right variant here because the first operand is signed and the second unsigned.

Recall Solution L5.3
  • Integer scaling ×100 = multiply ⇒ need M.
  • One shared counter touched by two concurrent tasks ⇒ atomic increment ⇒ need A.
  • "No fractional math" ⇒ omit F and D (FPU is silicon/power you don't spend).
  • Base integer ISA is always present: I.

ISA string: RV32IMA. You save the FPU area/energy — the whole point of RISC-V modularity: pay only for what the application uses. (C compressed could be added for code size, but the problem didn't require it.)


Recall Self-test summary

M splits a wide product into MUL (low) + MULH (high) — which variant? ::: The one matching operand signedness: MULH signed×signed, MULHU unsigned×unsigned, MULHSU signed×unsigned. Why does sc.w sometimes return 1? ::: Its reservation was invalidated by another core's write — this is normal contention, so you retry. When adding two floats, do you add the exponents? ::: No — you align to the common (larger) exponent by shifting the smaller mantissa; adding exponents is for multiply. Why did 2^24 + 1 stay 2^24 in single precision? ::: The ULP at that magnitude is 2, so +1 rounds to nearest-even back down; double has enough bits to represent it exactly.