PowerPC assembly language beginners guide.
Logical Operations
AND
Logical operations are quite simple to understand and form a useful tool within the programmers armory. An "and" operation simply says if both A and B are equal to a logical 1, then set the result to a 1. Logical operations work at a bit level - each bit of both operands is logically tested and the same numbered bit in the destination is set or cleared as a result of the logical operation as applied to each bit. We humans can work them out serially; by starting at the first bit of each operand and making a note of the result of each operation. The CPU operates on all bits in parallel, hence all logical operations (in common with most PowerPC instructions) have an effective processing time of one cycle. The exception to this timing are integer multiplies and divides - avoid these like the plague if you can or use the FPU which is a multiply-add unit and can perform single sized (32 bit) multiplies in one clock cycle effectively and double sized (64 bit) multiplies in two.
Back to the logic...
Suppose we "and" 1 and 9. If we look at the number in binary, 1 is 0001 and 9 is 1001. When these two numbers are anded, the processor looks at the numbers like this:
3210 <- Bit number
0001 <- 1 in binary
1001 <- 9 in binary
0001 <- result
First it will look at both bit 3's. It says I have a 1 and a 0, so I don't have two 1's. Therefore the result is zero. Then it looks at bit 2's, which are both zero, so the result is zero. Bits 1 are both zero, so the result is zero. Bits 0 are both 1. It says if I have a 1 "and" a 1 then the result is 1, so bit zero result is 1.
The result of 9 anded with 1 is 1.
The and operation can be summarised by saying "only if both bits are set will the result be set"
Examine the following examples, to see if you can spot the main use of the "and" instruction.
What is the result of 0xFA anded with 0x0F?
0xFA = 11111010 0x0F = 00001111 AND = 00001010 = 0x0A
What is the result of 0xF1 anded with 0x0F?
0xF1 = 11110001 0x0F = 00001111 AND = 00000001 = 0x01
What is the result of 0x1220 anded with 0x00FF
0x1220 = 0001001000100000 0x00FF = 0000000011111111 AND = 0000000000100000 = 0x0020
The and instruction is mostly used to mask off wanted data in a register. By setting bits in the mask that identifies the bits you want to keep, and then anding this mask with the data, the bits you are not interested in will be set to zero.
For example if you had a routine that returns the ASCII value of a key pressed on the keyboard, and it returned the key in r3. The key can be specified in a byte, but there may be data from earlier processing in the upper three bytes of r3 - so to ensure you don't create errors further in the program, the byte can be masked off with the and instruction as follows:
andi. r3,r3,0xff
Irrespective of how much garbage is in the upper 24 bits of r3, after this instruction all that will be left in r3 is the byte defining the key press, the lower 8 bits. Note in particular the trinary operands and the dot at the end of the instruction.
If we wanted we could leave the contents of r3 intact and place the results of the AND operation in another register, say r4, with an instruction such as:
andi. r4,r3,0xff
The dot means that this instruction always affects the condition code register; a note is made of the result of the operation. If for example, the result of the AND was that all bits were cleared, then the Z (or zero) flag inthe condition code register was set. We will examine the condition code register in more detail later, but for now just note that there are 8 integer condition register "fields". An instruction followed by a dot means that the condition code register field 0 is affected - it notes the result of the operation. NOTE also that immediate type instructions such as andi. always work with 16 bit unsigned immediate data. There are shifted forms of the instructions which will affect the upper 16 bits of a 32 bit register, as an example see the movei macro from chapter 2.
Examples of possible and instructions:
AND - and[.] rx,ry,rz
The contents of register rz is anded with register ry and the
resultant 32 bit pattern is stored in rx.
AND with complement - andc[.] rx,ry,rz
The contents of register ry are anded with the complement of rz
and the resultant 32 bit pattern is stored in rx. Complement means
the inverse of - for example %1010 when complemented becomes %0101.
Not to be confused with two's complement which is how computers
subtract via addition. Two complement means to invert the data
and then add 1.
AND Immediate - andi. rx,ry,ui
The contents of register ry are anded with the ui and the result
stored in rx. Note that in this case, the upper 16 bits of the
result will be cleared because ui is a 16 bit quantity! Note that
this instruction is always dotted.
AND Immediate shifted - andis. rx,ry,ui
This is basically the same as andi, except the ui is shifted left
16 bits before being anded with ry, so the lower 16 bits of rx
will always be cleared after this instruction.
OR
The OR instruction works like this:
If either or both of the bits are 1, then the result bit is a 1. The other way of looking at it is "If both bits are a zero then the result is a zero, otherwise its a 1".
Example - OR 1 with 2
1 = 0001
2 = 0010
OR= 0011 = 0x03
In PowerPC, the possible instructions are basically the same as and as follows:
OR immediate - ori rx,ry,ui (note no dot allowed unlike
andi. which must have one)
OR immediate shifted - oris rx,ry,ui (note no dot allowed
unlike andis. which must have one)
OR - or[.] rx,ry,rz
OR with complement - orc[.] rx,ry,rz
Fantasm uses the ori instruction to provide the "extended mnemonic" nop which stands for NO Operation:
nop is the same as ori 0,0,0
The or instruction is used to provide the useful extended mnemonic mr, which means move from one register to another:
mr rx,ry - move the contents of ry into rx and is the same as or rx,ry,ry
XOR
The eXclusive OR instruction works like:
"If one bit is a 1 and one bit is 0 then the result is 1, otherwise the result is 0".
Example - XOR 1 with 15
1 = 0001 15 = 1111 XOR= 1110 = decimal 14 or 0x0e
XOR 0 with 1
1 = 0000 0 = 0001 XOR= 0001, so the result is 1.
If we XOR the result with 1 again we get a 0 because both bits are now a 1. This is a neat way of toggling a bit, every time a loop executes for example. Initially the bit is set to 1. Each time round the loop, the bit is XOR'd with 1. Every time the loop executes. if the bit is a 1 its set to a 0, and if its a 0 its set to a 1.
What'sthe use of this? Suppose you want to flash something, say an alien spaceship on the screen between red and yellow. You simply xor the colour control bit with 1 and if it was a 1, use the colour red or if it was a zero use the colour yellow.
Possible XOR instructions:
xor immediate - xori rx,ry,ui
xor immediate shifted - xoris rx,ry,ui
xor - xor[.] rx,ry,rz
Miscellaneous logical instructions
NOR - NOT OR
NOT means to invert, so NOR performs an OR operation on the two operands, then inverts the result and stores it in the destination register:
nor[.] rx,ry,rz - the contents of ry are ORed with the contents of rz, then the result is inverted and stored in rx.
Fantasm uses the NOR instruction to provide the "extended instruction" NOT:
not rx,ry is the same as nor rx,ry,ry
NAND - NOT AND
NAND performs and AND operation on the two operands, inverts the result and stores it in the destination register.
nand[.] rx,ry,rz
EQV - Equivalent
eqv[.] rx,ry,rz - the contents of ry are XORed with the contents of rz, the result is inverted and stored in rx.
Sign extension instructions
Extend sign byte, Extend sign halfword, Extend sign word
extsb[.] rx,ry
extsh[.] rx,ry
extsw[.] rx,ry
These instructions copy the sized data (byte, half or word) to another or the same register and sign extend the result, so
extsb rx,ry copies the byte out of ry into rx and copies bit
7 to bits 8 through 31 of rx.
extsh rx,ry copies the lower 16 bits out of ry into rx and copies
bit 15 to bits 16 through 31 of rx.
extsw rx,ry is a 64 bit instruction that is illegal on 32 processors
such as 601/3/4. It copies the lower 32 bits out of ry into rx
and copies bit 31 to bits 32 through 63 of rx.
NOTE that Fantasm 5 will assemble most 64 bit instructions (i.e. PowerPC 620 processor) just fine - you can turn on warnings about their use from Fantasm's preferences pane.
Count Leading Zeros (Word or Double)
cntlzw[.] rx,ry
cntlzd[.] rx,ry
These two instructions, one for 32 bit architectures and one for 64 bit (the double form) counts how many zeros from the leftmost bit position until the first binary 1 is encountered. The count is stored in the destination register.
We mentioned about the dotted form of some instructions and that if the dot was used the condition flags would be set. Your first question may be why not use them all the time? The answer is simply that to set the condition register flags the processor needs to do more work. Sometimes you may not care if the result of a logical operation was zero or not - you just want the data. In this case you would not use the dotted form. Some times you do need to know so you can make a decision based on the outcome of the operation; in this case you would use the dotted form.
This will form the basis of the next chapter, where we'll be looking at the processor in more detail and examining the condition register and branch processor in general along with a closer look at the whole architecture and introducing the branch processor instructions. We will then have enough information "under our belts" to be able to produce some rudimentary programmes to introduce more integer instructions.
In the mean-time, it would be worthwhile revising the first three chapters and if you're not subscribed to the Fantasm list server maybe you'd like to consider it. Some very useful discussions crop up from time to time.