Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

MUL, DIV, REM — multiplication and division (M extension)

mul    rd, rs1, rs2       # rd = low XLEN bits of rs1 × rs2
mulh   rd, rs1, rs2       # rd = high XLEN bits of rs1 × rs2, signed × signed
mulhu  rd, rs1, rs2       # rd = high XLEN bits of rs1 × rs2, unsigned × unsigned
mulhsu rd, rs1, rs2       # rd = high XLEN bits of rs1 × rs2, signed × unsigned

div    rd, rs1, rs2       # rd = rs1 ÷ rs2, signed (truncate toward zero)
divu   rd, rs1, rs2       # rd = rs1 ÷ rs2, unsigned
rem    rd, rs1, rs2       # rd = rs1 mod rs2, signed (remainder same sign as rs1)
remu   rd, rs1, rs2       # rd = rs1 mod rs2, unsigned

Available in RV32M and RV64M. The M extension is typically included alongside the base ISA; most real cores implement it.

Multiplication — high half

A full XLEN × XLEN product is 2·XLEN bits wide. RISC-V splits the result across two destination registers:

Instructionrd getsUse
mulproduct[XLEN-1 : 0]standard multiply, low half
mulhproduct[2·XLEN-1 : XLEN]signed × signed high half
mulhuproduct[2·XLEN-1 : XLEN]unsigned × unsigned high half
mulhsuproduct[2·XLEN-1 : XLEN]signed × unsigned high half

When the low half alone is sufficient (e.g., boolean multiplication, pointer arithmetic, or any result that fits in XLEN bits), mul alone is correct regardless of signedness — the low bits of a product are the same for signed and unsigned operands.

RV32 (32-bit) example

rs1 = 0x8000_0001    (-2_147_483_647  signed / 2_147_483_649  unsigned)
rs2 = 0x0000_0002    (           2)

                   mul           mulh       mulhu       mulhsu
                  product[31:0]  product[63:32]
signed   × signed   0x0000_0002  0xFFFF_FFFF  —           —
unsigned × unsigned 0x0000_0002     —        0x0000_0001  —
signed   × unsigned 0x0000_0002     —           —        0xFFFF_FFFF

mul always writes the same 32-bit low-half result. Only the high-half instructions differ by interpretation.

MULHSU — signed × unsigned high half

mulhsu treats rs1 as signed and rs2 as unsigned. This is the instruction you reach for when you have a signed value that you want to multiply by a positive fraction stored as an unsigned fixed-point number.

     rs1 (signed,    XLEN bits)  ×  rs2 (unsigned, XLEN bits)
   ╭────────────────────────╮       ╭────────────────────────╮
   │  s  │  magnitude      │       │  magnitude              │
   ╰────┬──────────────────╯       ╰────────────────────────╯
        │                                  │
        │         signed × unsigned         │
        │    ───────────────────────        │
        │    treat rs1 as signed            │
        │    zero-extend rs2 to 2·XLEN      │
        │    multiply, sign-extend rs1      │
        │    result is signed, 2·XLEN bits  │
        │                                  │
        ╰──────────────┬───────────────────╯
                       │
            ┌──────────┴──────────┐
            │                     │
      mul ──┤product[XLEN-1:0]   │  same low half as mul/mulhu
            │                     │
 mulhsu ───┤product[2·XLEN-1:XLEN]│  high half (signed interpretation)
            └─────────────────────┘

Without mulhsu, software must derive the mixed-sign high half from other multiply and correction operations. mulhsu expresses it directly in one instruction.

# Multiply a signed 64-bit value (a0) by a 64-bit unsigned fraction (a1)
# stored as a Q64 fixed-point number (i.e., the unit bit is at 2^−64).
# The product is signed with 64 fractional bits.
mulhsu t0, a0, a1          # t0 = high half
mul    t1, a0, a1          # t1 = low half; sources remain unchanged

Division — edge cases

Division by zero

Instructionrd value
div rs1, x0−1 (all bits set)
divu rs1, x0(2^XLEN) − 1 (max unsigned value, all bits set)
rem rs1, x0rs1 (dividend)
remu rs1, x0rs1 (dividend)

No exception is raised. Software must check for zero divisor beforehand if it wants to trap.

Signed overflow: DIV with min / −1

When rs1 = INT_MIN (signed minimum, 0x8000_0000 in RV32 or 0x8000_0000_0000_0000 in RV64) and rs2 = −1, the mathematical quotient is −(INT_MIN) = INT_MAX + 1, which overflows the signed range. RISC-V defines this to produce rs1 (the dividend) rather than raising an exception.

Instructionrd value
div rs1, -1rs1 (the dividend — overflow)
rem rs1, -10
# Safe signed division that handles both edge cases
li   t0, -1
beq  a1, x0, handle_zero    # skip if divisor == 0
bne  a1, t0, 1f             # skip if divisor != -1
# divisor is -1: check for overflow
li   t0, 1
slli t0, t0, (XLEN - 1)     # INT_MIN
bne  a0, t0, 1f             # if dividend != INT_MIN, safe
j    overflow_path

1:
div  a0, a0, a1              # safe division

Remainder sign

The remainder always has the same sign as the dividend (rs1):

(rs1 ÷ rs2) × rs2 + (rs1 mod rs2) = rs1    # identity
sign(rs1 mod rs2) = sign(rs1)              # convention
li   a0, -7
li   a1, 3
div  t0, a0, a1          # t0 = -2  (truncate toward zero)
rem  t1, a0, a1          # t1 = -1  (same sign as dividend)
                         # check: (-2 × 3) + (-1) = -7 ✓

RV64 word operations

mulw   rd, rs1, rs2       # rd = sign_extend(product[31:0] of rs1[31:0] × rs2[31:0])
divw   rd, rs1, rs2       # rd = sign_extend(rs1[31:0] ÷ rs2[31:0]), signed
divuw  rd, rs1, rs2       # rd = sign_extend(rs1[31:0] ÷ rs2[31:0]), unsigned
remw   rd, rs1, rs2       # rd = sign_extend(rs1[31:0] mod rs2[31:0]), signed
remuw  rd, rs1, rs2       # rd = sign_extend(rs1[31:0] mod rs2[31:0]), unsigned

The *W variants treat their sources as 32-bit values and write a sign-extended 64-bit result to rd. They exist only in RV64M. There are no mulhw, mulhuw, or mulhsuw: when the operands have first been correctly sign- or zero-extended to 64 bits, ordinary RV64 mul produces the complete 32-by-32 product. If their upper halves are unknown, extend them first or use the shift-and-mulh technique described by the ISA specification.

Edge cases for word division follow the same rules as the full-width instructions but operate on 32-bit values:

# 32-bit signed division, result sign-extended to 64 bits
divw  a0, a1, a2          # compute a1[31:0] ÷ a2[31:0] → a0 = sign_extend(32-bit result)

# 32-bit division by zero → 0xFFFFFFFF → sign-extended to 0xFFFFFFFFFFFFFFFF
divw  a0, a1, x0          # a0 = −1

# 32-bit signed overflow: INT32_MIN / −1 → INT32_MIN → sign-extended
li    a1, 0x80000000      # INT32_MIN in a low 32 bits
li    a2, -1
divw  a0, a1, a2          # a0 = sign_extend(0x80000000) = 0xFFFFFFFF80000000

x86 connection

RISC-Vx86Notes
multwo-/three-operand imulboth can write the low half to a general register; low-half signedness is irrelevant
mulh / mulhuone-operand imul / mul high half (rdx)x86 uses the fixed rdx:rax pair; RISC-V writes the selected half to any rd
mulhsuno direct single instructionx86 has no signed-by-unsigned high-half form
div / divuidiv / divx86 uses a fixed dividend width (e.g., rdx:rax); RISC-V divides a single register by another
rem / remuidiv / div remainder (rdx)x86 returns remainder in rdx as a side effect of division; RISC-V has separate rem
mulwimul eax, ebxBoth compute a 32-bit product; x86 zero-extends, RISC-V sign-extends
divwidiv ecx with dividend in edx:eaxx86 uses a 64-bit dividend and writes quotient/remainder to eax/edx; RISC-V takes two low-32-bit sources and sign-extends its result

The main structural difference: x86 computes quotient and remainder together; RISC-V uses separate div / rem instructions. When both are needed, emitting div followed by rem with the same sources allows a supporting implementation to fuse them into one divide operation. The quotient destination must not overwrite either source before rem reads it.

Related: *W — RV64 word operations, SUB.