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 %?
17 (signed) · 0x00000011
5 (signed) · 0x00000005
| instruction | LO = quotient | HI = remainder | rule |
|---|---|---|---|
| div (signed) | 3 | 2 | quotient rounds toward zero; the remainder takes the dividend’s sign |
| divu (unsigned) | 3 | 2 | 17 ÷ 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?
-7 (signed) · 0xfffffff9 · 4294967289 if read unsigned
2 (signed) · 0x00000002
| instruction | LO = quotient | HI = remainder | rule |
|---|---|---|---|
| div (signed) | -3 | -1 | quotient rounds toward zero; the remainder takes the dividend’s sign |
| divu (unsigned) | 2147483644 | 1 | 4294967289 ÷ 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 → | 0xbfc00000 | 0x3c191000 | _start:lui $t9,0x1000 |
| 0xbfc00004 | 0x37390ff0 | ori $t9,$t9,0xff0 | |
| 0xbfc00008 | 0x24080011 | li $t0,17 | |
| 0xbfc0000c | 0x24090005 | li $t1,5 | |
| 0xbfc00010 | 0x0109001a | div $t0,$t1 | |
| 0xbfc00014 | 0x00005012 | mflo $t2 | |
| 0xbfc00018 | 0x00005810 | mfhi $t3 | |
| 0xbfc0001c | 0xaf2a0004 | sw $t2,4($t9) | |
| 0xbfc00020 | 0x24180020 | li $t8,32 | |
| 0xbfc00024 | 0xaf380000 | sw $t8,0($t9) | |
| 0xbfc00028 | 0xaf2b0004 | sw $t3,4($t9) | |
| 0xbfc0002c | 0xaf380000 | sw $t8,0($t9) | |
| 0xbfc00030 | 0x2408fff9 | li $t0,-7 | |
| 0xbfc00034 | 0x24090002 | li $t1,2 | |
| 0xbfc00038 | 0x0109001a | div $t0,$t1 | |
| 0xbfc0003c | 0x00005012 | mflo $t2 | |
| 0xbfc00040 | 0x00005810 | mfhi $t3 | |
| 0xbfc00044 | 0xaf2a0004 | sw $t2,4($t9) | |
| 0xbfc00048 | 0xaf380000 | sw $t8,0($t9) | |
| 0xbfc0004c | 0xaf2b0004 | sw $t3,4($t9) | |
| 0xbfc00050 | 0xaf380000 | sw $t8,0($t9) | |
| 0xbfc00054 | 0x24080006 | li $t0,6 | |
| 0xbfc00058 | 0x24090007 | li $t1,7 | |
| 0xbfc0005c | 0x71096002 | mul $t4,$t0,$t1 | |
| 0xbfc00060 | 0xaf2c0004 | sw $t4,4($t9) | |
| 0xbfc00064 | 0xaf200008 | sw $zero,8($t9) |
registers — read · written this tick
console (what the doors printed)
▮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?
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
- One div, two answers. / is mflo, % is mfhi. The remainder takes the dividend's sign: −7 / 2 is −3 remainder −1.
- 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.
- 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 answered1.After `div $t0,$t1` with $t0 = −7 and $t1 = 2, what are LO and HI?
2.What does GCC emit for `i * i` with int i, at -O0 and -O2 alike?
3.After `mul $t4,$t0,$t1`, what is in HI and LO?
4.What does MIPS say about `div $t0,$t1` when $t1 is 0?
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.