Archive

Assembly Language - Part 4



Chris Johnson

Assembler instructions

It is now time to start looking at the various assembler instructions in some detail. Wherever we make a start, we are going to have to make assumptions about areas that will be treated in detail later, so let us just take the plunge.

Data operations

Let us consider, first of all, instructions we may classify as data operations. The general format of these instructions is

instruction{condition}{S} destination, lhs, rhs

The instruction itself is usually a three letter code, such as ADD or MOV.

The condition is optional, but when present consists of a two letter condition code, such as EQ or HI, which makes the execution of the instruction conditional on the current state of the various result (status) flags. If the condition is not satisfied, then the instruction is not executed. If there is no condition specified, then the instruction is always executed.

The S, when present, causes this particular instruction to set the various result (status) flags, so that the execution of subsequent instructions can be made dependent upon the result of that instruction. When S is not present, then none of the result flags are affected.

Destination is the register where the result of the instruction is to be stored. The Basic assembler recognises, for example, either 0 or R0 for register 0. I shall use the Rx format to remind ourselves it is registers we are dealing with. We may also define our own variables for registers to make the code more readable, but care must be exercised to keep track of which registers are being used if we have defined several variables to refer to the same register. The Basic assembler also recognises PC for R15 (program counter), and link for R14 (the return address).

This group of instructions may be generally thought of as operating on two values in order to provide a result. These two values can be denoted as lhs and rhs. In arithmetic terms, we may denote the operation as "lhs operand rhs" For example, in the instruction

ADD R0, R1, R2

the lhs is R1, the rhs is R2, and the operand is ADD. Therefore we could think of the instruction as being

destination = lhs operand rhs

or

R0 = R1 + R2.

When commenting code, I shall often use this convention, i.e.

ADD R0, R1, R2  ; R0=R1+R2

I shall deal with the full set of condition codes a little later. However, just to illustrate how we could change the instruction by including one or both of the optional parts of the instruction, consider the following.

ADDEQ R0, R1, R2 ; only execute if zero flag (Z) is currently set, otherwise ignore instruction

ADDS R0, R1, R2 ; always execute, and set flags according to the result

ADDEQS R0, R1, R2 ; only execute if zero flag is set, and set the result flags

Destination and lhs must always be a register, whereas rhs may be either a register, or a constant (immediate operand). In most of these instructions, the registers involved may be any of R0 - R15 (although there are some differences when R15, the status and program counter, is involved), and the same register may be used in more than one position. For example, the instruction

ADD R0, R0, R0

is quite legal, and would result in the value held in R0 being doubled, i.e. R0 = R0 + R0.

Restrictions on constants

When a constant is used as the rhs operand, it must be prefixed by the # (hash) symbol. For example, to add 32 to the contents of R0 we could use:

ADD R0, R0, #32

In part one of the series, we emphasised that all ARM instructions consisted of a single 32-bit word. Within these 32-bits, we must include all the information on the operation itself, condition flags, and the operands, and this includes the value of any constant operand. This creates a problem, since after we have specified all the possibilities, there are only 12 bits left free to specify the value of the constant. 12 bits would allow us to use values of 0 to 4095 (or 2047 to -2048 if we are using signed numbers). Thus the range of numbers possible is very restricted. If we implemented immediate constants in this way, it would be difficult to set up masks, or access flags in the 20 other bits, using the available numbers.

When the original ARM cpu was designed, immediate constants were implemented in a different manner. Only 8 of the available 12 bits were allocated to define the actual value. This gives us values of 0 to 255 - even more restrictive on the face of it. However, the other 4 bits are used to give the 'position' of these 8 bits within the full 32 bits of a 32­bit constant. Since 4 bits corresponds only to values of 0 to 15, and we wish to access all 32 bits, the 8 bits are actually shifted by twice the value of the 'position'. It sounds complicated, but the assembler will actually work things out when the instruction is assembled. The shifting is an operation known as rotation, which we shall meet later. In this case, the rotation occurs to the right, with wrap around, i.e. what falls off the right of the number, comes back in at the left. Let us consider one or two examples. The number 207 can be represented in 32-bit binary as

0000 0000 0000 0000 0000 0000 1100 1111.

If we rotate this number 6 times to the right, feeding the values that fall out back in to the left end, we end up with

0011 1100 0000 0000 0000 0000 0000 0011.

This corresponds to the decimal number 1006632960. If instead we rotated 16 times we would have the binary number

0000 0000 1100 1111 0000 0000 0000 0000

which is 13565952 in decimal. Thus we can see how we can produce a much wider range of values using this rotation, and can access any particular bit. Unfortunately, we still can produce only a very limited range of numbers. In general, if the number can be represented by an eight bit number rotated by an even number of places, then we are all right. However, other numbers will provoke an error during assembly, with a message 'bad immediate constant'. For example the instruction

ADD R0, R0, #257

would fail since 257 in binary is

0000 0000 0000 0000 0000 0001 0000 0001

and there is no way we can represent this as an eight bit number, shifted or not, since the set bits spread across nine positions.

When we are faced with such a constant, we have to produce a workaround, by using more than one instruction. For example, 257 may be split into two parts, such as 256 + 1, both of which can be represented as eight bit numbers, so we could add 256, and follow it by adding 1. Not very convenient, but not the end of the world. Assemblers more intelligent than the inbuilt Basic assembler will produce such multiple instructions for us.

Logical instructions

I group together here several instructions that may be considered as carrying out logical operations. If S (set flags) is also present in the mnemonic, then the Z (zero) and N (negative) flags are affected by the operation, but not the C (carry) or V (overflow) flags. The C (carry) flag would be affected if the rhs operand is a register, and a shift or rotate is carried out on it as part of the instruction. This variation to the instruction we shall deal with in a later part of the series. The instructions are AND, ORR, EOR, and BIC.

AND - This carries out a bitwise AND of the two operands, and stores the result in the destination register. ANDing two bits produces a value 1 only if both bits are themselves 1, otherwise the result is 0. This can be represented in the form of a truth table.

Bit a Bit b Result
0 0 0
1 0 0
0 1 0
1 1 1

This operation is carried out across all 32 bits of the operands, i.e. bit 0 of the lhs is ANDed with bit 0 of rhs, and the result is stored in bit 0 of the destination, and so on for the other 31 bits.

Usage

AND R0, R0, #255	; retain only the lowest byte
AND R0, R0, #223	; mask is %11011111, which will convert a lower case letter to upper case (but will also affect punctuation characters, so use with care). Clearing specific bits can also be accomplished using the BIC instruction, see below.
ANDS R0, R1, R5 ; AND the contents of R1 and R5 and store the result in R0, i.e. R0 = R1 AND R5, and set the flags according to the result.

ORR - This carries out the logical OR of the operands. The truth table for the OR instruction is as follows.

Bit a Bit b Result
0 0 0
1 0 1
0 1 1
1 1 1

In other words, if either or both of the bits is set, then the result is 1. This operation is carried out across all 32 bits of the operands, i.e. bit 0 of the lhs is ORed with bit 0 of rhs, and the result is stored in bit 0 of the destination, and so on for the other 31 bits. This instruction is commonly used to set specific bits, which may be used as flags, without affecting any other bits.

Usage

ORR R0, R0, #%00100000 ; convert an upper case character to the corresponding lower case.

Note the bit mask is the inverse of the mask used above to convert from lower to upper case. Bit 5 will be set, whatever its current state, and all other bits will remain the same as they were.

EOR - This carries out the bitwise logical ExclusiveOR of the operands. The truth table is

Bit a Bit b Result
0 0 0
1 0 1
0 1 1
1 1 0

The result is 1 only if the two bits being EORed are different. The EOR instruction is often used to invert the state of selected bits, without any prior knowledge of what that state is.

Usage

EOR R0, R0, #%00100000 ; invert the case of a character, converting upper to lower case, and lower to upper case. I cannot think of a sensible use for this!

BIC - This instruction is used to clear selected bits, and store the result in the destination. The bits that are to be cleared are set in the rhs operand, and the operation is then lhs AND (NOT rhs). In other words, the bit pattern in the rhs is inverted first, and then ANDed with the lhs operand. For example, to convert lower case to upper case we could use

BIC R0, R0, #%00100000

rather than the AND instruction we used above.

AND R0, R0, #%11011111

Both these instructions have the same effect, i.e. clearing bit 5, although strictly, of course, the latter instruction should have a bit mask of %11111111 11111111 11111111 11011111 to leave the full 32 bits in the correct state. When dealing with printable characters, we made the assumption that the top 24 bits were all clear, and the ASCII value lies between 0 and 255.

Testing and comparing

In this subgroup, we have instructions that compare or test registers against a bit pattern or number. They are TEQ, TST, CMP and CMN. Since the whole purpose of these instructions is to perform a test and set the appropriate result flags, these instructions differ in their format from the previous group in two ways. Firstly, the S option is understood and does not have to be included explicitly. Secondly, there is no need to store the result anywhere (other than in the state of the flags), and so there is no need for a destination register. The format is therefore

instruction{condition} lhs, rhs.

TEQ - This instruction tests whether the two operands are the same, and sets the Z (zero) flag to 1 if the two operands are the same. Thus, in a subsequent instruction, we could use the EQ (equal to zero) condition if we were testing for equivalence, the NE (not equal to zero) condition if we wanted non-equivalence. Note that with this instruction only the Z flag is affected. In the following code fragment, we test to see if R2 and R3 are the same, and carry out different (fictitious) actions depending on the result.

TEQ R2, R3	  ; Are R2 and R3 the same?
ADDEQ R0, R0, #7  ; if so add 7 to R0
ADDNE R0, R0, R1  ; if not, set R0=R0+R1

This demonstrates how we can make more than one operation conditional on a particular test operation. This is generally much more efficient than branching over instructions, because of the pipelining in the cpu. Remember that the status flags are not changed again until either we explicitly change them using the optional S in an instruction, or we use one of these test/compare instructions which implicitly set the result flags.

TST - This instruction tests the specified bit pattern in the two operands, effectively ANDing the two operands, and setting the result flag.

Suppose we are writing a word processor, and we use a word of flags to record whether we are in normal text, or whether italic, bold, underlined, etc. styles are in use. We set (make = 1) bit 0 for bold, bit 1 for italic, bit 2 for underline. We can test whether any of these effects are in use by the following instruction, assuming the flag word is in R0.

TST R0, #%111

If the zero result flag is set (use EQ as the condition), then none of the styles is in use, i.e. all the bits are zero. If the zero flag is not set, then one or more styles must be in use, although we cannot say which ones from this test. If we wish to test whether, for example, italic style is in use (bit 1) then we would use the instruction

TST R0, #%010

Now the zero flag would be set if italic was not in use. Thus we could test for specific styles being in use by testing the corresponding bit.

CMP - This is used to compare two numbers. It subtracts the rhs from the lhs, and sets all the status flags as appropriate. The result is not stored anywhere. This instruction is more powerful than the TEQ instruction, since it can be used to find which of the numbers is the larger.

For illustration, suppose the user is asked to enter a parameter which must be within a range of valid values, say from 10 to 60. The value is held in R1, and we wish to trap invalid values. We could use the following.

CMP  R1, #10   ; compare R1 with the number 10
MOVLO R1, #10  ; if less than 10, change it to 10
CMP  R1, #60   ; compare R1 with 60
MOVHI R1, #60  ; if more than 60,
               ; change it to 60
		; At this point R1 must have a
		; value within the valid range.

We could equally well use registers as the rhs. Suppose the lower limit was stored in R5, and the upper limit in R6. Then the code would become:

CMP  R1, R5   ; compare R1 with R5
MOVLO R1, R5  ; if less than the
              ; valid range change
              ; it to equal R5
CMP  R1, R6   ; now compare R1 with R6
MOVHI R1, R6  ; if more than R6,
              ; change it to R6
		; at this point R1 must have a
		; value within the valid range

The CMP instruction works whether we are using signed or unsigned numbers. We shall deal fully with the various condition codes next time.

CMN - Compare negative. This is similar to CMP, but it negates the rhs operand before making the comparison. Its main use is therefore in comparing a register with a negative immediate operand. Note that we do not include the - sign in the instruction, the conversion to the negative is done for us. For example

CMN R0, #1	     ; compare R0 with -1
CMN R3, R6	     ; compare R3 with -R6

In the next part

Next time, besides meeting further ARM instructions, I shall deal with condition codes, and using negative numbers. By this time, we should have a reasonable basis of assembler to begin to use some slightly more substantial code snippets for illustration.


Contents - The Archives - Archive Articles