Arithmetic and Logic Instructions


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)


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)


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.


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.


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


    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]


    ; 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