In this chapter, we will go slowly through the MOV and ADD instructions to lay the groundwork on how they work, especially in the way they handle parameters (operands). So, in the following chapters, we can proceed at a faster pace, as we encounter the rest of the ARM instruction set.
Before getting into the MOV and ADD instructions, we will discuss the representation of negative numbers and the concepts of shifting and rotating bits.
Negative Numbers
In the previous chapter, we discussed how computers represent positive integers as binary numbers, called unsigned integers, but what about negative numbers? Our first thought might be to make 1 bit represent whether the number is positive or negative. This is simple, but it turns out it requires extra logic to implement, since now the CPU must look at the sign bits, then decide whether to add or subtract and in which order.
About Two’s Complement
The great mathematician John von Neumann, of the Manhattan Project, came up with the idea of the two’s complement representation for negative numbers, in 1945, when working on the Electronic Discrete Variable Automatic Computer (EDVAC)—one of the earliest electronic computers.
0x01 + 0xFF = 0x100
(all binary ones), we get 0x100.
0x01 + 0xFF = 0x00
2N – number
28 – 1 = 256 – 1 = 255 = 0xFF
0xFE + 1 = 0xFF
Two’s complement is an interesting mathematical oddity for integers that are limited to having a maximum value of one less than a power of two (which is all computer representations of integers).
5 + –3
3 in 1 byte is 0x03 or 0000 0011.
1111 1100
1111 1101 = 0xFD
5 + 0xFD = 0x102 = 2
Since we are limited to 1 byte or 8 bits.
About Gnome Programmer’s Calculator
Fortunately, we have computers to do the conversions and arithmetic for us, but when we see signed numbers in memory, we need to recognize what they are. The Gnome programmer’s calculator can calculate two’s complement for you. Figure 2-1 shows the Gnome calculator representing –3.
Note
The Gnome programmer’s calculator uses 64-bit representations.
About One’s Complement
If we don’t add 1 and just change all the 1s to 0s and vice versa, then this is called one’s complement . There are uses for the one’s complement form, and we will encounter it in how some instructions process their operands.
Big vs. Little-endian
01 00 00 00
00 00 00 01
00 00 00 01
01 00 00 00
About Bi-endian
The ARM CPU is called bi-endian , because it can do either. There is a program status flag in the CPSR that says which endianness to use. We’ll look at all the bits in the CPSR a bit later. By default, Raspbian and your programs use little-endian like Intel processors. You can change this if you want to. We’ll look at an application of changing this flag in a later chapter.
Pros of Little-endian
The advantage of little-endian format is that it makes it easy to change the size of integers, without requiring any address arithmetic. If you want to convert a 4-byte integer to a 1-byte integer, you take the first byte. Assuming the integer is in the range of 0–255, and the other 3 bytes are zero.
01 00 00 00
If we want the 1-byte representation of this number, we take the first byte; for the 16-bit representation, we take the first 2 bytes. The key point is that the memory address we use is the same in call cases, saving us an instruction cycle adjusting it.
When we are in the debugger, we will see more representations, and these will be pointed out again as we run into them.
Note
Even though Raspbian uses little-endian, many protocols like TCP/IP used on the Internet use big-endian and so require a transformation when moving data from the Raspberry Pi to the outside world.
Shifting and Rotating
We have 16 32-bit registers, and much of programming consists of manipulating the bits in these registers. Two extremely useful bit manipulations are shifting and rotating. Mathematically shifting all the bits left one spot is the same as multiplying by 2, and generally shifting n bits is equivalent to multiplying by 2n. Conversely, shifting bits to the right by n bits is equivalent to dividing by 2n.
0000 0011 (the binary representation of the number 3)
0011 0000
0x30 = 3 ∗ 16 = 3 ∗ 24
Now if we shift 0x30 right by 4 bits, we undo what we just did and see how it is equivalent to dividing by 24.
About Carry Flag
In the CPSR, there is a bit for carry . This is normally used to perform addition on larger numbers. If you add two 32-bit numbers and the result is larger than 32 bits, the carry flag is set. We’ll see how to use this when we look at addition in detail later in this chapter. When we shift and rotate, it turns out to be useful to include the carry flag. This means we can do a conditional logic based on the last bit shifted out of the register.
About the Barrel Shifter
Basics of Shifting and Rotating
- 1.
Logical shift left
- 2.
Logical shift right
- 3.
Arithmetic shift right
- 4.
Rotate right
- 5.
Rotate right extend
Logical Shift Left
This is quite straightforward, as we shift the bits left by the indicated number of places, and zeros come in from the right. The last bit shifted out ends up in the carry flag.
Logical Shift Right
Equally easy, here we shift the bits right, zeros come in from the left, and the last bit shifted out ends up in the carry flag.
Arithmetic Shift Right
The problem with logical shift right is, if it is a negative number, having a zero come in from the left suddenly turns the number positive. If we want to preserve the sign bit, use arithmetic shift right. Here a 1 comes in from the left, if the number is negative, and a 0 if it is positive. This is then the correct form if you are shifting signed integers.
Rotate Right
Rotating is like shifting, except the bits don’t go off the end; instead, they wrap around and reappear from the other side. So, rotate right shifts right, but the bits that leave on the right, reappear on the left.
Rotate Right Extend
Rotate right extend behaves like rotate right, except it treats the register as a 33-bit register, where the carry flag is the 33rd bit and is to the right of bit 0. This type of rotate is limited to moving 1 bit at a time; therefore, the number of bits is not specified on the instruction.
MOV/MVN
- 1.
MOV RD, #imm16
- 2.
MOVT RD, #imm16
- 3.
MOV RD, RS
- 4.
MOV RD, operand2
- 5.
MVN RD, operand2
We’ve seen examples of the first case, putting a small number into a register. Here the immediate value can be any 16-bit quantity, and it will be placed in the lower 16 bits of the specified register. This form of the MOV instruction is as simple as you can get; therefore, we will use it frequently.
About MOVT
Only two instructions, so not too painful, but a bit annoying.
Register to Register MOV
In the next case 3, we have a version that moves one register into another that sounds useful.
The Dreaded Flexible Operand2
All the ARM’s data processing instructions have the option of taking a flexible Operand2 as one of their parameters. At this point, it won’t be clear why you want some of this functionality, but as we encounter more instructions and start to build small programs, we’ll see how they help us. At the bit level, there is a lot of complexity here, but the people who designed the Assembler did a good job of providing syntax to hide a lot of this from us. Still, when doing Assembly programming, it’s good to always know what is going on under the covers.
- 1.
A register and a shift
- 2.
A small number and a rotation
Operand2 is processed via the barrel shifter, it’s just a matter of what is shifted and by how much.
Register and Shift
These assemble to the same byte code. The intent is that it makes the code a little more readable, since it is clear you are doing a shift or rotate operation and not just loading a register.
Small Number and Rotation
0 - 255 [0 - 0xff]
256,260,264,..,1020 [0x100-0x3fc, step 4, 0x40-0xff ror 30]
1024,1040,1056,..,4080 [0x400-0xff0, step 16, 0x40-0xff ror 28]
4096,4160, 4224,..,16320 [0x1000-0x3fc0, step 64, 0x40-0xff ror 26]
This is quite a clever scheme, as it lets you represent any power of 2 from 0 to 31, so you can set any individual bit in a register. It also lets you set any individual byte in a register. These turn out to be quite frequent scenarios, and you can specify it as part of most data processing instructions.
Fortunately, we don’t need to figure this all out. We just specify a number and the Assembler figures out how to represent it. Since there are only 12 bits, not all 32-bit numbers can be represented, so if you specify something that can’t be dealt with, then the Assembler gives you an error message. You then need to use a MOV/MOVT pair as outlined previously.
MOV has the advantage that it can take an #imm16 operand, which can usually get us out of trouble. However, other instructions that must specify a third register, like the ADD instruction, don’t have this luxury.
when you run your program through the Assembler. This means the Assembler tried all its tricks and failed to represent the number. To load this, you need to use an MOV/MOVT pair.
MVN
This is the Move Not instruction. It works just like MOV, except it reverses all the 1s and 0s as it loads the register. This means it loads the register with the one’s complement form of what you specified. Another way to say it is that it applies a logical NOT operation to each bit in the word you are loading into the register.
- 1.
To calculate the one’s complement of something for you. This has its uses, but does it warrant its own opcode?
- 2.
Multiply by –1. We saw that with the shift operations we can multiply or divide by powers of 2. This instruction gets us halfway to multiplying by –1. Remember that the negative of a number is the two’s complement of the number or the one’s complement plus one. This means we can multiply by –1 by doing this instruction, then add one. Why would we do this rather than use the Multiply (MUL) instruction? The same for shifting, why do that rather than using MUL? The answer is that the MUL instruction is quite slow and can take quite a few clock cycles to do its work. Shifting only takes one cycle, and using MVN and ADD, we can multiply by –1 in only two clock cycles. Multiplying by –1 is very common, and now we can do it quickly.
- 3.
You get twice the number of values due to the extra bit—13 vs. 12. It turns out that all the numbers obtained by using a byte value and even shift are different for MVN and MOV. This means that if the Assembler sees that the number you specified can’t be represented in a MOV instruction, then it tries to change it to an MVN instruction and vice versa. So, you really have 13 bits of immediate data, rather than 12. NOTE: It still might not be able to represent your number, and you may still need to use a MOV/MOVT pair .
MOV Examples
MOV examples
You can run the program after building it.
Note
This program doesn’t do anything besides move various numbers into registers.
We will look at how to see what is going on in Chapter 3, “Tooling Up,” when we cover the GNU Debugger (GDB).
Disassembly of the MOV examples
0xe
that means to always execute the instruction.
0x1a
Register
Register
Immediate
1101
1111
This includes the MVN we put in our source file and the MOV instruction that the Assembler changed to MVN so it could load –2.
The first two instructions that load 16-bit operands are different. Notice that the Assembler changed our first MOV into a Move Wide (MOVW) instruction. These aren’t part of the data processing instructions we are looking at now, and are special cases, but they are handy.
ADD/ADC
- 1.
ADD{S} Rd, Rs, Operand2
- 2.
ADD{S} Rd, Rs, #imm12
- 3.
ADD{S} Rd, Rs1, Rs2
- 4.
ADC{S} Rd, Rs, Operand2
- 5.
ADC{S} Rd, Rs1, Rs2
Registers
Operand2
#imm12
This saves some typing and is a bit clearer. This is a common scenario to increment loop counters.
An example of MVN and ADD
after running the program and it prints out 254. If you examine the bits, you will see this is the two’s complement form for –2 in 1 byte.
Add with Carry
The new concepts in this section are what the {S} after the instruction means along with why we have both ADD and ADC. This will be our first use of the CPSR.
- 1.
We first add 7 + 8 and get 15.
- 2.
We put 5 in our sum and carry the 1 to the tens column.
- 3.
Now we add 1 + 7 + the carry from the ones column, so we add 1+7+1 and get 9 for the tens column.
This is the idea behind the carry flag. When an addition overflows, it sets the carry flag, so we can include that in the sum of the next part. NOTE: A carry is always 0 or 1, so we only need a 1-bit flag for this.
The ARM processor adds 32 bits at a time, so we only need the carry flag if we are dealing with numbers larger than will fit into 32 bits. This means that even though we are in 32-bit mode, we can easily add 64-bit or even larger integers.
except that it sets various bits in the CPSR. We’ll cover all the bits when we cover conditional statements . For now, we are interested in the carry flag that is designated C. If the result of an addition is too large, then the C flag is set to 1; otherwise, it is set to 0.
The first ADDS adds the lower-order 32 bits and sets the carry flag if needed. It might set other flags in the CPSR, but we’ll worry about those later. The second instruction, ADDC, adds the higher-order words, plus the carry flag.
Example of 64-bit addition with ADD and ADC
0xFFFFFFFF
0x100000000
0x00000000
we get 9 as expected.
Learning about MOV was difficult, because this was the first time; we encountered both shifting and Operand2. With these behind us, learning about ADD was much easier. We still have some complicated topics to cover, but as we become more experienced with how to manipulate bits and bytes, the learning should become easier.
Summary
In this chapter, we learned how negative integers are represented in a computer. We went on to discuss big vs. little-endian byte ordering. We then looked at the concept of shifting and rotating the bits in a register.
Next, we looked in detail at the MOV instruction that allows us to move data around the CPU registers or load constants from the MOV instruction into a register. We discovered the tricks of operand2 on how ARM represents a large range of values, given the limited number of bits it has at its disposal.
Finally, we covered the ADD and ADC instructions and discussed how to add both 32- and 64-bit numbers.
In Chapter 3, “Tooling Up,” we will look at better ways to build our programs and start debugging our programs with the GNU Debugger (gdb) .