Skip to content
Contents

div, divu, mul and the rest of the unit

Section 7.2 9 min read

  • quotient in LO, remainder in HI
  • truncation toward zero
  • divide by zero: unpredictable
  • the release-2 mul that writes rd

Checked against: MIPS32 Vol. II: DIV, DIVU, MUL carolyne_ref/dumps/hello_o0.dump (bfc001bc mul v0,v0,v0) carolyne_ref/isa_mips/exec_unit_muldiv.py Carolyne compile_tool target.py (-mno-check-zero-division, -mno-imadd)

Before you start

  • §7.1: HI:LO and the four move instructions
  • §3.3: sra rounds toward −∞

By the end, you can

  • Give LO and HI after div for positive and negative operands
  • Say what MIPS promises about a divide by zero, and what your toolchain does about it
  • Tell mul from mult and say what each leaves in HI and LO
  • Read the compiler's mul for i × i

Two results from one instruction

div rs,rt divides signed; divu rs,rt divides unsigned. Like mult, both name no destination. The goes to LO and the goes to HI. C’s / is div + mflo; C’s % is div + mfhi.

17 ÷ 5: quotient 3 in LO, remainder 2 in HI

Which half is / and which is %?

bit 31bit 27bit 23bit 19bit 15bit 11bit 7bit 3
a (rs, the dividend)

17 (signed) · 0x00000011

b (rt, the divisor)

5 (signed) · 0x00000005

instructionLO = quotientHI = remainderrule
div (signed)32quotient rounds toward zero; the remainder takes the dividend’s sign
divu (unsigned)3217 ÷ 5
  • the sign bit, which div reads and divu ignores

Click any input bit to flip it.

What to notice

div and divu agree here because both operands are small and positive. Both rows show LO = 3 and HI = 2. Flip bit 31 of the dividend and the two rows split: the signed row sees a negative number, the unsigned row a huge one.

Rounding toward zero

What is −7 ÷ 2? The exact answer is −3.5, and div uses : the quotient is −3. The remainder then follows from dividend = quotient × divisor + remainder: −7 = (−3)(2) + (−1), so HI = −1. The remainder always takes the dividend’s sign.

−7 ÷ 2: quotient −3, remainder −1

Why is the remainder negative?

bit 31bit 27bit 23bit 19bit 15bit 11bit 7bit 3
a (rs, the dividend)

-7 (signed) · 0xfffffff9 · 4294967289 if read unsigned

b (rt, the divisor)

2 (signed) · 0x00000002

instructionLO = quotientHI = remainderrule
div (signed)-3-1quotient rounds toward zero; the remainder takes the dividend’s sign
divu (unsigned)214748364414294967289 ÷ 2
  • the sign bit, which div reads and divu ignores

Click any input bit to flip it.

What to notice

The signed row gives −3 and −1. The unsigned row reads 0xFFFFFFF9 as 4294967289 and gives 2147483644 remainder 1. Same bits, two divisions. Note also that sra by 1 would have given −4: shifts round the other way (§3.3).

One corner: INT_MIN / −1 should be +2147483648, which does not fit. MIPS gives INT_MIN back with remainder 0 and raises nothing. Unlike add, div never traps.

Watch div, then mul

The runner divides 17 by 5 and −7 by 2, printing both halves each time, then multiplies with the release-2 mul. It pauses before the negative divide.

div puts the quotient in LO and the remainder in HI; mul writes a register directly

Where do a quotient and a remainder go, and what is different about the release-2 mul?

program (address · word · instruction)

PC →0xbfc000000x3c191000_start:lui $t9,0x1000
0xbfc000040x37390ff0ori $t9,$t9,0xff0
0xbfc000080x24080011li $t0,17
0xbfc0000c0x24090005li $t1,5
0xbfc000100x0109001adiv $t0,$t1
0xbfc000140x00005012mflo $t2
0xbfc000180x00005810mfhi $t3
0xbfc0001c0xaf2a0004sw $t2,4($t9)
0xbfc000200x24180020li $t8,32
0xbfc000240xaf380000sw $t8,0($t9)
0xbfc000280xaf2b0004sw $t3,4($t9)
0xbfc0002c0xaf380000sw $t8,0($t9)
0xbfc000300x2408fff9li $t0,-7
0xbfc000340x24090002li $t1,2
0xbfc000380x0109001adiv $t0,$t1
0xbfc0003c0x00005012mflo $t2
0xbfc000400x00005810mfhi $t3
0xbfc000440xaf2a0004sw $t2,4($t9)
0xbfc000480xaf380000sw $t8,0($t9)
0xbfc0004c0xaf2b0004sw $t3,4($t9)
0xbfc000500xaf380000sw $t8,0($t9)
0xbfc000540x24080006li $t0,6
0xbfc000580x24090007li $t1,7
0xbfc0005c0x71096002mul $t4,$t0,$t1
0xbfc000600xaf2c0004sw $t4,4($t9)
0xbfc000640xaf200008sw $zero,8($t9)

registers — read · written this tick

HI 0x00000000LO 0x00000000PC 0xbfc00000

console (what the doors printed)

▮
cycle 0/261 tick = 1 instruction (not a cycle)

tick 0 of 26

Nothing has run yet. The PC (program counter) points at the first instruction.

div, like mult, changes no general register: LO takes the quotient, HI the remainder, and mflo/mfhi fetch them. 17 ÷ 5 gives 3 and 2. −7 ÷ 2 gives −3 and −1: the quotient rounds toward zero and the remainder takes the dividend's sign. The release-2 mul writes 42 straight into $t4 with no HI/LO step.

What to notice

At each div, only the HI and LO chips change. The prints show 3 and 2, then −3 and −1. The last mul is different: $t4 turns green at once, with no mflo in between, and the HI and LO chips are not part of the story.

Before reading on, decide:

A C program computes `17 % 5`. After `div $t0,$t1`, which instruction fetches the answer, and what is it?

Divide by zero: unpredictable

What does div do when the divisor is 0? MIPS says the result is : HI and LO may hold anything, and no exception is raised. Because C programs expect something to happen, GCC normally inserts a teq instruction after every div that traps when the divisor is zero. Your toolchain passes -mno-check-zero-division, so no teq appears in your dumps, and teq is outside the instruction set your core implements. The site’s simulator, for determinism, returns the RISC-V answers (LO = −1, HI = the dividend) and flags the tick.

Release 2’s mul

MIPS32 release 2 added . It computes the low 32 bits of the product and writes them to rd, no mflo needed. For C’s int × int, which only ever wants the low word, that is one instruction instead of two. GCC emits it for i * i in hello.c:

mul v0,v0,v0 from hello.c (0x70421002 at 0xBFC001BC)

Which opcode table is this, and what is different from mult's word?

3126
opcode6b
011100
28 = SPECIAL2
2521
rs5b
00010
$v0 ($2)
2016
rt5b
00010
$v0 ($2)
1511
rd5b
00010
$v0 ($2)
106
sa5b
00000
0
50
funct6b
000010
2 = mul
binary01110000010000100001000000000010
hex0x70421002one hex digit = four bits; a digit that spans two fields has two colors
asmmul $v0,$v0,$v0R-format
Hover or focus a field to see its bits, value and role.

What to notice

Opcode 28, SPECIAL2: the release-2 table. funct 2 is mul. Unlike mult, the rd field is used: v0. In the 34-build census mul appears 37 times and mult not once; in the fourteen reference dumps, this is the only multiply.

Two cautions. After mul, HI and LO are unpredictable: it may or may not have used them. And mul gives only the low word; a program that needs the high word still uses mult. Release 2 also has madd and msub, which multiply and accumulate into HI:LO; your project excludes them because they would need four sources, so GCC is told -mno-imadd and none appear.

Go deeper: how your core runs mult, div and mul, and why the divider matters

Carolyne runs all nine of these µops on a fourth station of its own, the muldiv station, in order and one at a time. The body is combinational: * on operands widened to 64 bits gives mult’s two words in one step, and / and % run on magnitudes with the sign put back, which yields INT_MIN / −1 = INT_MIN with no special case. A divisor of zero is muxed to the RISC-V answers so the hardware never produces X. The unit exists on both the MIPS and RISC-V descriptions; the only MIPS-specific part is that mult and div name two destinations, hi and lo, where RISC-V’s mul names one.

The combinational divider is the longest path in the core and will bound its clock speed until a sequential divider replaces it, a recorded open item. That is the same tension the 1985 designers faced: a slow divider parked in its own registers so the rest of the pipeline could keep moving.

Can you now explain it?

div gives quotient (LO) and remainder (HI) together, rounding toward zero; nothing traps, not even divide by zero, which is simply undefined. mul is release 2's shortcut: the low word into rd, HI:LO forgotten.

If you remember only three things

  1. One div, two answers. / is mflo, % is mfhi. The remainder takes the dividend's sign: −7 / 2 is −3 remainder −1.
  2. Nothing traps here. INT_MIN / −1 wraps, 0 as divisor is unpredictable. The teq check is the compiler's, and your build turns it off.
  3. mul is not mult. mul writes rd and forgets HI:LO; mult fills HI:LO and needs mflo. Compiled code uses mul.

Now try to explain this one yourself: C’s x / 2 for a signed x could be div + mflo, or a single sra x,x,1. For x = −7 the two give different answers. Which, and what must a compiler add to make the shift correct?

Go deeper: a model answer, once you have tried

div gives −3, truncated toward zero, which is what C requires. sra by 1 gives −4, because a shift rounds toward −∞. To use the cheap shift, the compiler first adds 1 to x when x is negative: (−7 + 1) >> 1 = −3. In MIPS that is srl t,x,31 (the sign bit as 0 or 1), addu t,x,t, then sra t,t,1. Three fast instructions instead of one slow divide, and the same answer as div for every x. Optimised code is full of this pattern.

Check your understanding

0/5 answered
  1. 1.After `div $t0,$t1` with $t0 = −7 and $t1 = 2, what are LO and HI?

  2. 2.What does GCC emit for `i * i` with int i, at -O0 and -O2 alike?

  3. 3.After `mul $t4,$t0,$t1`, what is in HI and LO?

  4. 4.What does MIPS say about `div $t0,$t1` when $t1 is 0?

  5. 5.C code computes `a % b` with signed ints. Which MIPS instructions does it become?


Next: Chapter 8 — conditional moves and sign-extension: movz, movn, seb, seh.

Where this comes back

2 later sections build on this one.