Multiply and divide, with the remainder
The same program in M68K, MIPS, Z80, x86.
Two unit conversions, one in each direction. The first turns 365 days into hours with a mul, and
the second turns 1000 seconds into 16 minutes and 40 seconds, with the minutes from a div and the
leftover seconds from a rem.
The arithmetic examples so far have mostly added and subtracted. Multiplication and division are the M extension: a group of instructions that a RISC-V chip is allowed to leave out, and that this simulator has. That is the deal the whole instruction set is built on, a small core everyone implements and named groups on top of it.
.text
main:
li t0, 365
li t1, 24
mul t2, t0, t1 # hours in a year
mulh t3, t0, t1 # the top half of the same product
li t4, 1000
li t5, 60
div t6, t4, t5 # whole minutes
rem s0, t4, t5 # the seconds left over
li s1, 0
div s2, t0, s1 # a division by zero, on purpose
rem s3, t0, s1
Multiply two 32 bit numbers and the answer can need 64 bits. A register holds 32, so the product
comes in two pieces and each piece is an instruction: mul writes the bottom half, mulh writes
the top half of the same multiplication. For 365 times 24 the top half is all zeroes, since 8760
fits with room to spare, and the second instruction looks pointless.
Change the 24 to 12000000 and it stops looking pointless. t2 comes out at 05117F00 and t3 at
1, and the real answer is that 1 times 4294967296, plus 85032704, which is 4380000000. The low half
alone has lost the part that did not fit. The high-half instruction says how to interpret each
operand: mulh is signed times signed, mulhu is unsigned times unsigned, and mulhsu treats its
first operand as signed and its second as unsigned.
div and rem split the same way, one instruction per answer, over the same two source registers.
Division cuts towards zero and the remainder keeps the sign of the number being divided, so -7 over
2 is -3 with a remainder of -1. divu and remu are the pair that read their operands as plain
unsigned values from 0 through 4,294,967,295.
The last two lines divide by zero, and the program does not stop, complain, or print anything. div
answers FFFFFFFF, which is -1, and rem hands back the number you were dividing. That is the
defined behaviour, not a bug in the simulator, and it means a program handed a zero it did not
expect carries on quietly with a wrong answer. If a divisor could be zero, check it and branch to
the handling your program needs before doing the division.
Signed division has one more special case. The smallest signed 32-bit number is 0x80000000, or
-2,147,483,648, and its positive counterpart cannot fit in a signed register. Dividing it by -1
therefore leaves 0x80000000 as the quotient, and rem returns 0. These results, like the
zero-divisor results, are defined by RISC-V.
Your turn: split seconds into hours, minutes, and seconds
Your program receives a nonnegative signed 32-bit duration in t0. Write the divisions that leave
whole hours in t1, the minutes left after those hours in t2, and the final leftover seconds in
t3. Use 3,600 and 60 as divisors, and use rem to carry only the leftover part into the next
calculation.
.text
main:
# split the duration in t0 into t1 hours, t2 minutes, and t3 seconds
Show solution
.text
main:
li t4, 3600
div t1, t0, t4
rem t5, t0, t4
li t4, 60
div t2, t5, t4
rem t3, t5, t4