5.1.8 · D5Instruction Set Architecture (ISA)

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

2,242 words10 min readBack to topic

Before we start, one plain-word refresher so nothing is used undefined:

The bit-level pictures below back up the multiplication, LR/SC, and floating-point traps — look at them as you work.


Pictures that power the traps

One product, two halves. A 32×32 multiply makes a 64-bit result. MUL grabs the low half, MULH* the high half of the same product:

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

The LR/SC retry loop. LR raises a reservation; if another core writes the line, SC fails and the loop retries with fresh data:

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

Float addition alignment. Before mantissas can be added they must share an exponent — shift the smaller one right:

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

IEEE-754 exponent map. The stored exponent field carves the number line into regions — zero/subnormal, normal, and infinity/NaN:

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

See 5.2.03-IEEE-754-floating-point for the full treatment.


True or false — justify

The base ISA already includes integer multiply, so the M extension only adds divide.
False — the plain base ISA (5.1.07-RISC-V-base-ISA) has no hardware multiply either; M adds both MUL* and DIV*. A base-only chip does multiply in a shift-add software loop.
MUL and MULH compute two independent multiplications.
False — they compute one full multiplication whose product is twice as wide as a register; MUL returns the low half, MULH the high half of the same product (see the two-halves figure).
If you only need the low 32 bits of a 32×32 product, the signedness of the operands doesn't matter.
True — the low half of a two's-complement product is bit-identical whether you treat inputs as signed or unsigned, which is exactly why there's only one MUL but three MULH variants.
LR.W followed by SC.W guarantees the store will succeed.
False — SC may fail (return non-zero) if another core touched the reserved line, or even spuriously; the point of the retry loop is that failure is expected and normal.
amoadd.w and an LR/SC loop that adds are functionally interchangeable, so choose whichever.
Mostly true functionally, but amoadd.w is a single instruction that locks the line directly, while LR/SC is a multi-instruction loop that can livelock under heavy contention — prefer the AMO for simple patterns.
The A extension needs no help from the memory system; it's purely a CPU feature.
False — atomics ride on the cache coherence protocol. The reservation is invalidated precisely because coherence already broadcasts writes to a line.
The F extension's registers f0f31 are the same registers as the integer x0x31.
False — F adds a separate floating-point register file. You must explicitly move data across with FMV/load-store; they don't overlap.
Any decimal you write in source code has an exact single-precision representation.
False — only numbers expressible as (integer)×2ᵏ within the mantissa width are exact. 0.1 and 0.2 repeat forever in binary, so they're stored rounded (see 5.2.03-IEEE-754-floating-point).
Double precision is just "single precision but slower"; the number range is the same.
False — D has an 11-bit exponent (bias 1023) versus F's 8-bit (bias 127), so the range grows from ≈±10³⁸ to ≈±10³⁰⁸, not just the precision.
A chip can implement RV32IMAC while skipping F and D entirely.
True — extensions are independently implementable. Skipping float saves the FPU and the extra register file; the M, A, C parts don't depend on F/D.
Signed division DIV of MIN_INT by −1 raises an exception, just like division by zero does on x86.
False — RISC-V has no divide traps at all. MIN_INT ÷ −1 overflows the representable range so it is defined to return MIN_INT (and REM returns 0); no fault occurs.

Spot the error

mulh t1, a0, a1 where a0, a1 hold two unsigned magnitudes — what's wrong?
MULH sign-extends both operands, so if either has its top bit set it's misread as negative. For unsigned inputs use MULHU.
A programmer writes sc.w without ever running lr.w first, expecting a plain conditional store.
SC only means anything relative to a prior LR reservation; with no reservation raised it will simply fail (or behave undefined by contract). It is not a general "store-if" instruction.
Inside an LR/SC retry loop, someone inserts a normal load/store to an unrelated address between the lr.w and sc.w.
This is dangerous — the architecture only guarantees forward progress for a very short, restricted instruction sequence between LR and SC; stray memory accesses can cause the reservation to be lost every time, creating an infinite loop.
Storing the true exponent −126 for a single-precision number directly as a signed 8-bit field.
Exponents are stored biased: you store −126 + 127 = 1, an unsigned value. Raw signed exponents would break the "compare as unsigned integers to compare magnitudes" trick.
Claiming 0x80000000 × 0x80000000 overflows and can't be represented.
It fits fine: (−2³¹)² = 2⁶², which lives in the 64-bit product. MULH returns the high half 0x40000000; nothing overflows because the product width is doubled by design.
Testing floating equality with if (0.1 + 0.2 == 0.3) and expecting true.
Rounding makes 0.1 + 0.2 come out as 0.30000000000000004; float comparisons must use a tolerance, never exact ==.
Handling divide-by-zero by wrapping every DIV in a "hardware will fault, so I'll catch the exception" pattern.
There is no exception to catch — RISC-V defines DIV x, 0 to return all-ones (−1), DIVU x, 0 all-ones too, and REM/REMU x, 0 to return the dividend. You must test the divisor in software before dividing if you want error handling.

Why questions

Why does MULH come in three flavours (MULH, MULHU, MULHSU) but MUL has only one?
The high half of a product depends on how the sign bits are interpreted, so signed/unsigned/mixed give different top halves — while the low half is identical for all interpretations, so one MUL suffices.
Why did RISC-V choose to define divide-by-zero results instead of trapping?
Traps complicate pipelines and require exception machinery; defining a fixed return value keeps division a plain, side-effect-free instruction and lets software decide whether to check — matching RISC-V's minimalist philosophy.
Why can't you build a lock in user mode by just "disabling interrupts around the critical section"?
Disabling interrupts is a privileged operation unavailable to user code, and it also does nothing to stop another core from racing you. Atomics (A extension) solve the multi-core race directly.
Why does the A extension "piggyback" on cache coherence instead of adding new hardware?
Coherence protocols like MESI already track and broadcast every write to a cache line, so the reservation just listens to that existing traffic — no new signalling network is needed.
Why is the floating-point exponent biased instead of stored as a signed two's-complement number?
Biasing makes the bit pattern monotonic — comparing two floats' magnitudes reduces to a plain unsigned integer compare of their bit patterns, which hardware and sorting can exploit.
Why does the result of 0.1 + 0.2 == 0.3 depend on the rounding mode?
Every inexact operation rounds according to the current mode; the default round-to-nearest-even rounds 0.1+0.2 up to ...0004, but a mode like round-toward-zero could land on a different last bit — so "which floats are equal" is mode-dependent.
Why separate F and D into two extensions instead of one "floating point" extension?
Doubling register width and datapath to 64 bits costs real silicon area and power. Embedded audio/sensor chips often need only single precision, so splitting lets them pay only for what they use.
Why does hardware floating-point addition need an "align exponents" step at all?
You can only add mantissas that represent the same power of two; aligning shifts the smaller number's mantissa right until both share an exponent, so the bits line up by place value (see the alignment figure).
Why is the leading mantissa bit "implicit" (not stored) in normalized floats?
A normalized number is always 1.something × 2ᵏ, so the leading 1 is guaranteed and storing it would waste a bit — dropping it buys one extra bit of precision for free.

Edge cases

What does SC return, and what does its value mean at the boundary between success and failure?
It writes 0 into rd on success (store happened, atomicity held) and non-zero (typically 1) on failure (reservation lost); zero being success lets bnez branch straight to the retry.
Two cores both spin in an LR/SC increment loop forever — is progress guaranteed?
The architecture guarantees only that some core eventually succeeds (system-wide forward progress) for well-formed loops, not that a particular core does — one core can be repeatedly beaten but the counter always advances.
What is the defined result of DIV MIN_INT, −1 and REM MIN_INT, −1 on RV32?
DIV returns MIN_INT (0x80000000) because the true quotient +2³¹ doesn't fit, and REM returns 0; both are specified, no overflow flag or trap.
Reserved exponent fields: what do the all-zero and all-ones exponents mean in IEEE 754?
All-zeros encodes subnormals (and ±0), all-ones encodes ±∞ and NaN; these are carved out of the exponent range (see the exponent-map figure), which is why single precision's usable exponents run only −126…+127, not the full 8-bit span.
Multiplying so the true product needs both halves — how do you reconstruct the full value from MUL and MULH?
The full product equals the high half shifted up by the register width plus the low half — i.e. MULH sits above MUL when you concatenate the two words.
What happens to a running single-precision sum after ~10⁶ additions of numbers around the same magnitude?
Each add rounds to 23 mantissa bits, and errors accumulate to roughly a percent of the total; switching the accumulator to double (52 bits) shrinks the error by many orders of magnitude even if the inputs stay single.
Can a compressed-instruction (C) chip also lack multiply?
Yes — C only re-encodes existing instructions into 16-bit forms for code density (3.3.06-Instruction-encoding); it adds no arithmetic capability, so RV32C without M still has no hardware multiply.
Is MULHSU symmetric — can you swap which operand is signed?
No. MULHSU fixes rs1 as signed and rs2 as unsigned; swapping the roles changes the interpretation and generally the result, so operand order is meaningful here.
Recall Quick self-check

Why is the low half of a product signedness-independent? ::: Because two's-complement addition/multiplication produce identical low bits regardless of sign interpretation — only the high (sign-carrying) bits differ. What flag makes atomics work, and who invalidates it? ::: The per-core reservation flag, invalidated by any other core's coherence write to the reserved line. Why bias 127 for single precision? ::: It's , centring the exponent range and letting exponents be compared as unsigned integers. What does RISC-V do on divide-by-zero? ::: Nothing exceptional — it returns a defined value (−1 for DIV, the dividend for REM); no trap, so check in software.