Overview
The Cortex-M architecture supports common arithmetic and logical instructions that can be used to modify the contents of the general purpose registers. Most instructions in the Cortex-M architecture are single cycle instructions.
In addition to arithmetic operations, there are a variety of logical operations that are supported. The most common are AND, ORR, MVN (logical NOT), EOR (exclusive OR). Logical operations are commonly used to set (make a bit 1) or clear (make a bit a 0) a specific bit in a register. Each operation is a 32-bit operation. For example, lets assume that R0 = 0xFFFF3333 and R1 = 0xFF00FF00. If we AND R0 and R1 the result would be 0xFF003300
1111 1111 1111 1111 0011 0011 0011 0011 (R0) & 1111 1111 0000 0000 1111 1111 0000 0000 (R1) 1111 1111 0000 0000 0011 0011 0000 0000 (Result)
Syntax
The general syntax for arithmetic/logic instructions is as follows
<OP> Rd, Rn, <Operand2>
- <OP> is replaced with the desired operation (ex. ADD).
- Rd is the destination register where the results will be written to.
- Rn is one of the source registers.
- <Operand2> is what ARM refers to as its flexible 2nd operand. This value can be another register, a immediate value (constant), or a register with a shift operation.
The flexible second operand supports adding two registers together or adding an immediate value to a single register. Immediate values are required to start with a #. Immediate values can be written in decimal, hexadecimal, and rarely in binary.
ADD R2, R1, R0 ; R2 = R1 + R0 ADD R2, R2, #1 ; R2 = R2 + 1 (Decimal immediate) AND R2, R2, #0xFF ; R2 = R2 & 0x000000FF (Hexidecimal immediate) AND R2, R2, #2_0011 ; R2 = R2 & 0x00000003 (Binary immediate)
Immediates
When you begin to write ARM assembly code, you will undoubtedly encounter some assembler errors for invalid immediates. A good question would be “Why are some immediates valid and others invalid?”. The answer to that has to do with the fact that the ARM architecture is a RISC architecture. This means that all assembly instructions get encoded into a machine instruction (0’s or 1’s) of a fixed length. In our case, that length can be 16 or 32-bits. If an instruction specifies a destination register, source register, and a 32-bit immediate value, how does all of the information get encoded into a 32-bit machine instruction? The answer is that it can’t. We would need all 32-bits just to specify the immediate, leaving no room for the op code and other information needed to determine the source and destination registers.
What ARM has decided to do is limit which immediates are valid. Valid immediates can take one of 4 forms.
- Any immediate that can be produced by shifting an 8-bit value left by any number of bits within a 32-bit word ( X and Y are any hexadecimal number)
- Any immediate of the form 0x00XY00XY
- Any immediate of the form 0xXY00XY00
- Any immediate of the form 0xXYXYXYXY
For a more in depth description of why ARM chose to use the 8-bit value with a 4-bit shift code, see the following link.
Signed Vs. Unsigned Numbers
The numbers that are manipulated by a microprocessor can be treated as either unsigned or a signed numbers. Unsigned numbers use all bits in the number to represent a positive. The registers in the Cortex-M architecture are 32-bits wide, but the following examples will examine 8-bit numbers for the sake of simplicity.
If a register were to hold a value of 111111012 , then the value would represent a decimal value of 253 (1×27 + 1×26 +1×25 + 1×24 +1×23 + 1×22 +0x21 + 1×20).This is because an unsigned number uses all of the bits to represent a positive number. Using the same value of 111111012 , what decimal value would this number represent for a signed number? The answer is -3.
Signed numbers are integer numbers that can be either positive numbers or negative numbers. Positive signed numbers will always start with a 0 as the most significant digit and their magnitude can be determined in the same way as an unsigned number. Signed numbers are represented using 2’s compliment number representation. A negative 2’s compliment number will always start with the most significant bit as a 1. In order to calculate the magnitude of the negative number, you can invert all the bits in the number and add one to it.
11111101 ? Bitwise Compliment(11111101) + 1 ? 00000010 + 1 ? 00000011 (-310)
The reason that 2’s compliment numbers are used in microprocessors is that subtraction using 2’s compliment numbers is really the same thing as addition. The following examples will demonstrate this.
2 00000010 + 3_ + 00000011 00000101 (510)2 00000010 – 3_ + 11111101 11111111 ?00000000 + 1 ?(-110)
Initializing a General Purpose Register
In order to initialize a register to a known value, you can use a MOV instruction. A MOV instruction sets the value of the destination register to the value of a different register OR it can set it to a value of an immediate. MOV instructions can support any 16-bit immediate. So what do you do if you want to initialize a register to a 32-bit immediate that is not a valid immediate? The answer is to use the MOV32 command. MOV32 is a pseudo instruction. A pseudo instruction is an instruction that the assembler supports that is not actually supported by the hardware. The assembler replaces the pseudo instruction with one or more supported commands that will accomplish the desired results. In the case of a MOV32, the assembler uses two commands to load a 32-bit immediate ( MOV and MOVT).
Optional Shift of 2nd Operand
The Cortex-M architecture supports shifting the 2nd operand prior to executing the desired op code. There are 5 variations of a shift that are supported.
- ASR #n (Aithmetic Shift Right)
- LSL #n (Logical Shift Left)
- LSR #n (Logical Shift Right)
- ROR #n (Rotate right)
- RRX (Rotate right one bit with extend)
MOV R0, #1 ; R0 = 1 MOV R1, #2 ; R1 = 2 ADD R1, R1, R0, LSL #2 ; R1 = R1 +(R0<<2)= 2+(1<<2) = 6
See page 28 of the TI Cortex-M Instruction Set for more specifics of each shift operation.
Multiply/Divide
Some MCUs implement multiplication as a multi-cycle operation and others do not support a native multiply instruction at all. In the case where there is no native multiply instruction, the compiler turns the multiplication operation into a FOR loop that repeatedly adds. The ARM Cortex-M architecture supports a a single cycle 32-bit multiply that helps to improve the overall performance of an application. Divide operations and multiply operations that are greater than 32-bit require multiple clock cycles to complete.
Other Commands
The Cortex-M architecture supports other instructions not mentioned here. For a complete listing of instructions please see the ARM Assembly Referecne. If you want to see more examples than the ones provided below, please see the TI Cortex-M Instruction Set.
Examples
Valid Constants
ADD R0, R0, #1 ADD R0, R0, #0xFF000000 SUB R1, R0, #0xAB00AB00 ADD R2, R1, #0x12121212 SUB R2, R0, #0x000FE000
Initializing General Purpose Registers
MOV R0, #0xFFFF ; Sets bits 15-0 to 0xFFFF MOVT R0, #0xEEEE ; Sets bits 31-16 to 0xEEEE MOV32 R0, #0xEEEEFFFF ; Equivalent Pseudo Instruction MOV R1, R0 ; Initialize from a register
Adding/Subtracting
MOV R0, #1 ; R0 = 1 MOV R1, #10 ; R1 = 10 ADD R2, R1, R0 ; R2 = R1 + R0 (11) SUB R3, R1, R0 ; R3 = R1 - R0 (9)
Adding Large Numbers
;****************************************************** ; ADC Rd, Rn, <op2> ; Add with Carry allows for adding numbers larger than ; 32 bits ;****************************************************** ; Initialize the registers for the first 64-bit number MVN R0, #0x0 ; Sets R0 to be 0xFFFFFFFF MOV R1, #0x0 ; Sets R1 to be 0x00000000 ; Initialize the registers for the second 64-bit number MOV R2, #0x1 ; Sets R0 to be 0x00000001 MOV R3, #0x1 ; Sets R1 to be 0x00000001 ; 64-bit Addition ; 0x0000000100000001 ; + 0x00000000FFFFFFFF ADD R4, R0, R2 ; R4 contains SUM[31:0] ADC R5, R1, R3 ; R5 contains SUM[63:32]
Multiply/Divide
;****************************************************** ; MUL Rd, Rn, Rm ; Multiply two 32-bit number, result is 32-bit ;****************************************************** MOV R0, #10 MOV R1, #5 MUL R2, R1, R0 ;****************************************************** ; SMULL RdLo, RdHi, Rn, Rm ; Multiply two 32-bit number, result is 64-bit signed ; integer ;****************************************************** MOV R0, #0x80000000 MOV R1, #8 SMULL R2, R3, R1, R0 ;****************************************************** ; UMULL RdLo, RdHi, Rn, Rm ; Multiply two 32-bit number, result is 64-bit unsigned ; integer ;****************************************************** MOV R0, #0x80000000 MOV R1, #8 UMULL R2, R3, R1, R0 ;****************************************************** ; SDIV Rd, Rn, Rm ; Divide two 32-bit number, result is 32-bit signed ; integer ;****************************************************** MOV R1, #0x80000000 MOV R0, #8 SDIV R2, R1, R0 ;****************************************************** ; UDIV Rd, Rn, Rm ; Divide two 32-bit number, result is 32-bit unsigned ; integer ;****************************************************** MOV R1, #0x80000000 MOV R0, #8 UDIV R2, R1, R0
Clearing Bits
; Clear bits 3:0 MOV R0, #0xFFFF ; Initialize R0 to 0x0000FFFF MVN R1, #0x0F ; Set Bit Mask to 0xFFFFFFF0 AND R0, R0, R1 ; Clear bits 3:0 ; A different (more efficient) way to clear bits 3:0 MOV R0, #0xFFFF ; Initialize R0 to 0x0000FFFF BIC R0, R0, #0x0F ; Clear bits 3:0
Setting Bits
; Set bits 3:0 to a value of 1 MOV R0, #0 ORR R0, R0, #0xF
Toggling Bits
; Toggle ( Invert) bits 15:8 MOV R0, #0xFF ; Set R0 to 0x000000FF ; An Exclusive OR will toggle the bits of a register where ; the immediate is set to a 1. EOR R0, R0, #0xFF00 ; Exclusive-OR results in 15:8