M lives entirely in R-type encodings under OP/OP-32 with funct7 = MULDIV — no new formats, no new state, and true to RISC-V form,
no traps: every input, including division by zero, produces a defined
register result. It’s a separate extension so divider-less
microcontrollers stay cheap; a Linux-class RV64GC core implements all of
it.
| Semantics | Designer note | |
|---|---|---|
| MUL | rd = (rs1 × rs2)[XLEN−1:0] | Signedness-agnostic for the low half |
| MULH / MULHU / MULHSU | Upper XLEN bits of the 2×XLEN product: s×s / u×u / s×u | MULHSU = multi-word signed multiply helper |
| MULW (RV64) | sext32(rs1[31:0] × rs2[31:0]) | W rules from ch. I·4 apply |
| DIV / DIVU | Quotient, rounding toward zero | dividend = divisor×q + r (except overflow) |
| REM / REMU | Remainder; sign of nonzero REM = sign of dividend | Pairs with DIV via the fusion idiom |
| DIV[U]W / REM[U]W (RV64) | 32÷32, result sext32 — always, even on divide-by-zero | The sign-extension invariant holds on exceptional paths too |
Fusion idioms — the ISA’s sanctioned macro-ops: need both halves of a
product? MULH[SU|U] rdh, rs1, rs2; MUL rdl, rs1, rs2 — same operand
order, rdh distinct from both sources. Need quotient and remainder?
DIV[U] rdq; REM[U] rdr under the same rules. Decoders can recognize the
pair and issue one multiply/divide.
Table 11: the no-trap arithmetic contract
| DIVU[W] | REMU[W] | DIV[W] | REM[W] | |
|---|---|---|---|---|
| Divide by zero (any dividend x) | 2^L − 1 | x (the dividend) | −1 | x (the dividend) |
| Signed overflow: −2^(L−1) ÷ −1 | — (cannot occur) | — | −2^(L−1) | 0 |
Zmmul: multiply without divide
Zmmul = exactly the multiplication subset (MUL, MULH, MULHU, MULHSU, MULW), identical encodings; M implies Zmmul. It exists for cores where division is too rare to fund a divider — notably FPGA soft cores, where multipliers are hardened DSP blocks but a divider burns soft logic.
Hardware Designer Notes
The pragmatic v1: a single-cycle or pipelined multiplier (your FPGA/ASIC
library decides) and a simple non-restoring sequential divider that stalls
the pipe. Divide-by-zero must NOT early-out to a different result path —
let the unsigned core run and it produces the all-1s/dividend pair
naturally. The subtle RV64 check: REMW x, y, x0-style cases must come
back sign-extended 32-bit values, not raw 64-bit passthroughs.
Minimal Linux-boot hart MUST
- Return Table 11 values exactly — reference models diff them; no trap paths exist
- REMW/DIVW results sign-extend to 64 bits on ALL paths, including divide-by-zero constants
- Round quotients toward zero; REM sign follows the dividend
MAY simplify / trap-and-emulate
- Implement one unsigned divider core + sign fixups — Table 11 semantics then fall out free
- Iterate: a 32-64-cycle sequential divider is entirely respectable for Linux boot; nothing times div
- Detect the MULH+MUL / DIV+REM fusion pairs in decode — or not; it is purely a performance option
- Ship Zmmul-only on a microcontroller profile — but a Linux-class core needs full M (it is the M in RV64GC)
Check yourself — M extension
1.DIV x5, x6, x0 executes (divide by zero). What happens?
2.Why does the ISA even define MULHSU?
3.Which sequence lets your microarchitecture compute high and low product halves with ONE multiply?
4.REMW x5, x6, x0 on RV64 (32-bit remainder of divide-by-zero). What lands in x5?