Arithmetic, logic and bits

Add, subtract, multiply and divide, and then the operators that work one bit at a time: and, or, xor, not and the shifts. Masks, and how a program asks about a single bit.

The loops and the branches we wrote counted with add and compared with cmp, which is most of what a program does between two jumps. Let's look at the rest of the instructions that compute.

Arithmetic

add and sub are what they look like. Multiplication and division are the ones with a catch, because multiplying two 32 bit numbers can need 64 bits for the answer and dividing gives back two answers, a quotient and a remainder.

The M68K's answer is to work on halves: mulu multiplies the low 16 bits of a data register by a 16 bit operand and writes the 32 bit product over the whole register, and divu divides the whole 32 bit register by a 16 bit operand and packs both answers into it, the quotient in the low word and the remainder in the high word. The u is for unsigned, muls and divs are the signed pair.

d0 ends at 10 and d1 at 0000002A, which is 42. d2 ends at 00030006: read it as two words and the low one is 0006, the quotient, and the high one is 0003, the remainder, because 45 is 7 times 6 with 3 left over. Press W in the registers panel and the two words are drawn apart.

RISC-V has 32 bit registers and a separate instruction per answer, so it reads much closer to the C:

t3 is 42, t5 is 6 and t6 is 3. Every one of these takes three operands, destination first, which is the shape of nearly every RISC-V and MIPS instruction: the two it reads and the one it writes are all named, and none of them is overwritten unless you say so.

Logic, one bit at a time

and, or, xor and not are the C operators &, |, ^ and ~, and they work on each of the 32 bit positions on its own, with no carrying between them.

aba AND ba OR ba XOR b
00000
01011
10011
11110

The M68K spells xor eor, for exclusive or, and the versions that take a plain number as their first operand end in i, for immediate: andi, ori, eori. RISC-V and MIPS call them and, or, xor and the immediate forms andi, ori, xori, and get not by exclusive-oring with -1.

d0 is 1000, which is 8, d1 is 1110, which is 14, and d2 is 0110, which is 6. d3 comes out at FFFFFFF3, because not flipped all 32 bits and not only the four you wrote.

Masks, shifts and one bit

A mask is a number you write for the pattern of its bits, one bit set per bit you want to touch, and the three operators above are the three things you do with one:

  • and with a mask keeps the bits the mask has set and clears the rest. x & 0xFF in C.
  • or with a mask sets those bits and leaves the rest alone. x | 4.
  • xor with a mask flips them. x ^ 2.

A shift slides every bit sideways, which is x << 2 and x >> 2 in C. Shifting left by n multiplies by 2 to the n and shifting right by n divides by it, which is why they turn up wherever an index has to be scaled to a byte offset. The M68K writes them lsl and lsr, and has asr for the signed shift right, which drags the sign bit along instead of feeding in zeroes, so asr.l #2 on -20 gives -5 where lsr.l #2 gives 1073741819. MIPS and RISC-V spell the same three sll, srl and sra, and RISC-V puts an i on the end when the amount is a constant: slli, srli, srai.

Together they read a piece out of the middle of a value: slide it down to the bottom, then mask off what is above it.

d0 comes out at 00000078 and d1 at 00000056, the two lowest bytes of 12345678 pulled out one at a time. d2 goes 1010, then 1110, then 1100, which is 12.

The last line is how a program asks about one bit. In C you write if (z & 1), and on the M68K btst #0, d2 does the and and throws the answer away, keeping only the zero flag, exactly like cmp does with a subtraction. It sets Z to 1 when the bit is 0, so beq after it means "the bit was clear" and bne means "the bit was set". Here bit 0 of 1100 is 0, so Z is 1.

Try changing btst #0, d2 to btst #2, d2 and watch Z go to 0, because bit 2 of 1100 is set.