"M": Integer Multiplication and Division

Part I Linux boot: required Vol. I (Unprivileged) pp. 65–66 · ~4 min read

  • mulh family
  • division-by-zero semantics
  • zmmul

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.

The M instruction set
SemanticsDesigner note
MULrd = (rs1 × rs2)[XLEN−1:0]Signedness-agnostic for the low half
MULH / MULHU / MULHSUUpper XLEN bits of the 2×XLEN product: s×s / u×u / s×uMULHSU = multi-word signed multiply helper
MULW (RV64)sext32(rs1[31:0] × rs2[31:0])W rules from ch. I·4 apply
DIV / DIVUQuotient, rounding toward zerodividend = divisor×q + r (except overflow)
REM / REMURemainder; sign of nonzero REM = sign of dividendPairs with DIV via the fusion idiom
DIV[U]W / REM[U]W (RV64)32÷32, result sext32 — always, even on divide-by-zeroThe sign-extension invariant holds on exceptional paths too
Dotted-underlined cells have explanations — click one.

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

Division by zero and signed overflow (L = operation width)
DIVU[W]REMU[W]DIV[W]REM[W]
Divide by zero (any dividend x)2^L − 1x (the dividend)−1x (the dividend)
Signed overflow: −2^(L−1) ÷ −1— (cannot occur)−2^(L−1)0
Dotted-underlined cells have explanations — click one.

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?

4 questions