Arithmetic, logic and bits
Multiplication and division each write one ordinary register, so the top half of a product takes a second instruction, and a division by zero says nothing at all. Then the masks, the shifts and the fields you pull out of a word with them.
add and sub behave exactly as they look, and they wrap without a word of complaint, which "Words,
halves and bytes" showed. Multiplication and division are the two with rules of their own, because a
product can need 64 bits and a division has two answers.
Two instructions for one product
Multiplying two 32 bit numbers gives a 64 bit answer, and a RISC-V instruction writes one register. So there are two instructions, and you run whichever half you want, or both:
mulwrites the low 32 bits of the product.mulhwrites the top 32 bits, reading both operands as signed.mulhudoes the same reading both as unsigned, andmulhsuwith the first signed and the second unsigned.
Division splits the same way, and here the two answers are different questions rather than two halves of one:
divwrites the quotient andremthe remainder, both signed.divuandremuread the operands as unsigned.
t2 comes out at 540BE400 and t3 at 2, which together are 10000000000. s0 is 142 and s1 is
6, because 7 times 142 is 994.
The MIPS course keeps its product in hi and lo, two registers outside the 32 that only four
instructions can reach, and a mult between a div and its mflo throws the quotient away. There
is nothing like that here: mul and div write the register you named, and nothing else on the
machine is touched.
The cost is that a program wanting the whole 64 bit product runs the multiplication twice, once as
mul and once as mulh. A chip is allowed to notice the pair and do the work once.
Division by zero says nothing
div by zero does not stop the program, does not raise an exception and does not need a guard.
RISC-V wrote the answers into the specification instead:
| what you wrote | quotient | remainder |
|---|---|---|
x / 0 | -1, all ones | x, the dividend |
the most negative word / -1 | itself, wrapped | 0 |
t2 and t4 come out at FFFFFFFF, t3 at 10, s0 at 80000000 and s1 at 0. Nothing stopped
and nothing was reported.
MIPS is the opposite: its three operand div and rem are pseudo-instructions that assemble a
break in front of the real division, so a divisor of zero ends the run with a message. Here the
program carries on with a -1, so a divisor that could be zero is yours to test, with a beqz
before the div.
Logic and masks
and, or and xor are one bit position at a time with no carrying between them, and the i forms
take a constant: andi, ori, xori. There is no not instruction, because xori t1, t0, -1 is
one, and the assembler accepts not as a name for it.
A mask is a number written for the pattern of its bits, and each of the operations does one of the things you can want with one:
andwith a mask keeps the bits the mask has set and clears the rest.orwith a mask sets those bits.xorwith a mask flips them.
The constant of andi, ori and xori is 12 bits sign extended, which cuts both ways. It only
reaches -2048 to 2047, so a mask like 0xFF00 has to go into a register with li first. But because
it is sign extended, a mask of all ones at the top is cheap: andi t1, t0, -256 is FFFFFF00, so
clearing the low byte is one instruction.
t2 comes out at 00000056 and t3 at 00000034, the second and third bytes of 12345678 pulled
out one at a time. t6 is 0000ABCD, two bytes packed into one half. s0 is 123456FF, s1 is
12345687 and s2 is 12345600.
Mask and shift down to read a field, shift up and or to write one. That pair is how every packed
value on this machine is taken apart, including the colour of a pixel on the bitmap display, which is
red, green and blue in three bytes of one word.
Shifts
Three shifts, each with a constant form and a register form:
sll,slli, shift left, zeroes coming in at the bottom. Shifting left bynmultiplies by 2 to then.srl,srli, shift right logical, zeroes coming in at the top, which divides an unsigned number.sra,srai, shift right arithmetic, copies of the sign bit coming in at the top, which divides a signed number.
The constant amount is five bits, so 0 to 31, and the register forms use the low five bits of the register and ignore the rest.
There is no rotate. MIPS has rol and ror as pseudo-instructions and the M68K has four real
rotate instructions; the RISC-V base has none at all, so a rotate is a shift each way and an or,
written by you.
t2 is 00100000, t4 is 0000FF00, t6 is FFFFFFFB, which is -5, and s0 is 3FFFFFFB,
which is 1073741819. s4 is C0000000: the bit at the bottom of s1 came round to the top and
joined the one already there.
One bit at a time
There is no bit test instruction. Testing bit n is andi with 1 << n and a branch on whether the
answer is zero; setting it is ori, clearing it is andi with the complement, flipping it is
xori. When n is in a register, srl brings the bit down to the bottom and andi t1, t1, 1 keeps
it.
Counting the set bits of a word is that idea in a loop:
t1 comes out at 16 and t0 at 0, shifted away entirely. add t1, t1, t3 with a bit that is 0 or 1
is the whole of "count it if it is set", which needs no branch.
MIPS has clz and clo, which count the run of zeroes or ones at the top of a word in one
instruction. The RISC-V base has neither, and neither does this assembler, so finding the highest set
bit is a loop like the one above.
Your turn
The test starts t0 at 100000. Leave the whole product of t0 times itself in two registers:
its low 32 bits in t1, which is 0x540BE400, and its top 32 bits in t2, which is 2.
Show solution
The second one starts t0 at 0xF0F0F0F0 and wants the number of bits set in it left in t1, which
is 16. t0 may be destroyed on the way.